How do I fire React useEffect hook only once after state change?
Asked Answered
B

3

6

I am new to React hooks and I am not sure how to achieve following goal. Let's say I have state1 and state2, and I use useEffect hook to call asyncFn1 and update state1.

Now I want to wait for state1 change and use state1 value to call asyncFn2 and update both state1 and state2. This asnycFn1 and asyncFn2 should only be called once.

If I just use another useEffect to call asyncFn2, I won't get the state1 value. How do I solve that?

const [state1, setState1] = useState(null);
const [state2, setState2] = useState(null);

const asyncFn1 = async() => {
  // async call to get state 1 data
  // setState1
}

const asyncFn2 = async(_state1) => {
  // use state1 data to make async call to get state 2 data
  // state2 data will be used to update both state1 and state2
}

useEffect(() => {
  asyncFn1();
}, [])
Berardo answered 25/4, 2021 at 7:1 Comment(4)
Why do you want to update state1 from asyncFn2? Does the input _state1 to asyncFn2 differ from the state you want to call setState1 with?Rothermere
See #59493126, same logic, have a ref which acts as "called once" flag.Semilunar
@PatrickRoberts Yes the state 1 will cause different asyncFn2 result which will be used to update the state1 again.Berardo
@DennisVash thank you! useRef is exactly what I needed.Berardo
W
9

What you need here is a useEffect which has your state1 in the useEffect dependency array, so to trigger it any time your state1 value changes, as such:

useEffect(() => {
   state1 && asyncFn2()
}, [state1])

In case you want your asyncFn2 to trigger only once after you get the data for state1, you can just add a ref to check when that's being called:

const dataLoaded = useRef(false)

useEffect(() => {
   if(state1 && !dataLoaded.current) {
      asyncFn2()
   }
}, [state1])

const asyncFn2 = async () => {
   // Your logic here

   // Set dataLoaded on true once you have updated what you need successfully
   dataLoaded.current = true
}
Wappes answered 25/4, 2021 at 7:17 Comment(4)
I see you fixed your example, but my statement is not wrong, previously you called useCallback inside useEffect, try this code and tell me about your findingsSemilunar
also, it should be !dataLoadedRef.current instead !dataLoadedRef which is always a truthy statementSemilunar
True, that's a good point thanks for pointing - but what you said about not using hooks inside functions, what are functional components then?Wappes
In React terms, there are "Function Components" and when saying "functions" those are just JS functions, see reactjs.org/docs/hooks-rules.html "Don’t call Hooks from regular JavaScript functions"Semilunar
S
0

I do this quite often and found that for me the most readable (and understandable) method is to use a hook that stores the previous value of something.

export function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

Then when I want create a side effect only when one specific value in my state changes I write:

const [fetching, setFetching] = useState(false);
const prevFetching = usePrevious(fetching);

useEffect(() => {
  if (prevFetching === true && fetching === false) {
    // Do something
  }
}, [fetching, prevFetching]);
Samhita answered 29/3, 2023 at 12:7 Comment(0)
E
0

To wait for the result of asyncFn1 to fetch the data for asyncFn2 you can use IIFE inside the useEffect and wait for asyncFn1 to complete executing and use the result to make the call to asyncFn2.

useEffect(() => {
    (async function(){
      const result1 = await asyncFn1();
      // Use the result to fetch the data for asyncFn2 
      const result2 = await asyncFn2();
      // Update both state1 and state2
     })()
    }, [])
Eupatrid answered 14/8, 2023 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.