5

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();
}, [])
Orio Ryo
  • 132
  • 1
  • 2
  • 8
  • 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? – Patrick Roberts Apr 25 '21 at 07:10
  • 1
    See https://stackoverflow.com/questions/59492626/stop-useeffect-from-running-on-mount/59492738#59492738, same logic, have a ref which acts as "called once" flag. – Dennis Vash Apr 25 '21 at 07:29
  • @PatrickRoberts Yes the state 1 will cause different asyncFn2 result which will be used to update the state1 again. – Orio Ryo Apr 26 '21 at 02:10
  • @DennisVash thank you! useRef is exactly what I needed. – Orio Ryo Apr 26 '21 at 02:12

3 Answers3

8

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
}
ale917k
  • 1,494
  • 7
  • 18
  • 37
  • 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 findings – Dennis Vash Apr 25 '21 at 07:45
  • 1
    also, it should be `!dataLoadedRef.current` instead `!dataLoadedRef` which is always a truthy statement – Dennis Vash Apr 25 '21 at 07:46
  • True, that's a good point thanks for pointing - but what you said about not using hooks inside functions, what are functional components then? – ale917k Apr 25 '21 at 07:48
  • 1
    In React terms, there are "Function Components" and when saying "functions" those are just JS functions, see https://reactjs.org/docs/hooks-rules.html "Don’t call Hooks from regular JavaScript functions" – Dennis Vash Apr 25 '21 at 08:47
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]);
Andreas Riedmüller
  • 1,217
  • 13
  • 17
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
     })()
    }, [])