0

Question: I am loading data from an API into my functional app component with useEffect (example below). I can't understand how to then set the variable activeToken because phrases is not yet defined, perhaps because useEffect is async?

What pattern should I be using to ensure that phrases exists before I invoke activeToken?

function App() {

  const getActiveToken = (tokens) => {
    for(let i = 0; i < tokens.length; i++) {
      if (tokens[i].mask === true) {
        return i
      }
    }
  }

  useEffect(() => {
    fetch('http://localhost:5000/api/v1')
      .then((response) => response.json())
      .then((data) => setPhrases(data))
  }
  )


  const [termIndex, setTermIndex] = useState(0);
  const [phrases, setPhrases] = useState([]);
  const [activeToken, setActiveToken] = useState(getActiveToken(phrases[termIndex].tokens))}
7
  • getActiveToken doesn't return anything and doesn't have a side effect, so what's it's purpose? Commented Apr 5, 2021 at 16:11
  • setPhrases is used before it is defined Commented Apr 5, 2021 at 16:13
  • As far as I can tell there is no issue of using an async function in a useEffect, since you are using then, not an await async function. Commented Apr 5, 2021 at 16:14
  • 1
    @BenCarp - getActiveToken just provides an index where the first .mask attribute in some of my data is true. It gets called later throughout the application but I don't think is fundamental to my problem. Commented Apr 5, 2021 at 16:14
  • If you can make your question a bit more clear I'd be happy to suggest an answer. Commented Apr 5, 2021 at 16:14

3 Answers 3

1

You are trying to set the initial state of activeToken from a state that you would update asynchronously (phases). Since phases is not ready when the component mounts, it won't be updated when phases changes, because the initial value is only used at the 1st render (useState initialization).

Since activeToken is derived from the tokens, which is derived from phases, you can compute it in the body of the component, and wrap it with useMemo, so it would only be computed when tokens change, but it's not strictly necessary unless tokens is huge. In addition the expression phrases[termIndex].tokens would throw an error, because phrases[termIndex] is undefined.

const getActiveToken = (tokens = []) => {
  for(let i = 0; i < tokens.length; i++) {
    if (tokens[i].mask === true) {
      return i
    }
  }
}

function App() {
  useEffect(() => {
    fetch('http://localhost:5000/api/v1')
      .then((response) => response.json())
      .then((data) => setPhrases(data))
  });   

  const [termIndex, setTermIndex] = useState(0);
  const [phrases, setPhrases] = useState([]);

  const tokens = phrases[termIndex]?.tokens;
  
  const activeToken = useMemo(() => getActiveToken(tokens), [tokens]);
Sign up to request clarification or add additional context in comments.

Comments

0

Make a seperate async function containing async code and call it in useEffect.

//seperate function
const fetcher = async () => {
    fetch('http://localhost:5000/api/v1')
      .then((response) => response.json())
      .then((data) => setPhrases(data))
}

useEffect(() => {
   fetcher();
}

You can also

useEffect(() => {
    //within useEffect
    let fetcher = async () => {
        fetch('http://localhost:5000/api/v1')
          .then((response) => response.json())
          .then((data) => setPhrases(data))
    }

    fetcher();
}

Comments

0

Try this dirction: Try the following:

function App() {

  const [activeToken, setActiveToken] = useState() // activeToken can't be set in the initial run anyway

  useEffect(() => {
    fetch('http://localhost:5000/api/v1')
      .then((response) => response.json())
      .then((tokens) => {
        for(let i = 0; i < tokens.length; i++) {
        if (tokens[i].mask === true) {
          setActiveToken(tokens[i])
        }
      }



  })

}
  

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.