jrjames83
jrjames83

Reputation: 901

React Hooks - useState using the result of another hook var set during useEffect

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))}

Upvotes: 0

Views: 437

Answers (3)

Ben Carp
Ben Carp

Reputation: 26608

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])
        }
      }



  })

}
  

Upvotes: 0

Ori Drori
Ori Drori

Reputation: 193318

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]);

Upvotes: 1

Shreyas Jadhav
Shreyas Jadhav

Reputation: 2571

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();
}

Upvotes: 0

Related Questions