Clashing useEffects?

Viewed 212

I feel like I come across this issue often.

When chaining useEffects to be triggered after state changes, I find that some of the useEffects in the chain have overlapping dependencies which cause them both to be triggered rather than sequentially, after one sets to state.

Example: I am triggering a chain of useEffects when chapterIndex updates.

let [ chapterIndex, setChapterIndex ] = useState<number | null>(0);

let [textTransitioning, setTextTransitioning] = useState<'out' | 'in' | null>(null);

useEffect(() => {
    setTextTransitioning('out')
}, [chapterIndex])

useEffect(() => {
    if (chapterIndex !== null) {
        const {
            current: chapter = null
        } = chapterRefs.current[chapterIndex];
            
        if (textTransitioning === 'in') {
            chapter?.classList.add('in');
        }
    }
}, [textTransitioning, chapterIndex])

How do I prevent both from running when chapterIndex changes? I only want the second useEffect to run after the first useEffect sets to textTransitioning. When removing chapterIndex from the dependency array I get the error: 'React Hook useEffect has a missing dependency: 'chapterIndex'. Either include it or remove the dependency array.'

2 Answers

You need to remove "chapterIndex" from array in second useEffect. So when first useEffect will run then it will updated the "textTransitioning" that will cause second useEffect trigger.

For more conditional execution of useEffect you can refer https://reactjs.org/docs/hooks-effect.html

useEffect takes 2 arguments. first is the function to invoke and the second is an array of variables called dependencies array. the second argument used for triggering the use effect while one of the variables in the array is changing. so if you don't want useEffect to be called when a specific variable is changed do not pass it in the dependencies array. your code should be like that:

let [ chapterIndex, setChapterIndex ] = useState<number | null>(0);

let [textTransitioning, setTextTransitioning] = useState<'out' | 'in' | null>(null);

useEffect(() => {
      setTextTransitioning('out')
}, [chapterIndex])

useEffect(() => {
 if (chapterIndex !== null) {
     const {
         current: chapter = null
     } = chapterRefs.current[chapterIndex];
         
     if (textTransitioning === 'in') {
         chapter?.classList.add('in');
     }
 }
}, [textTransitioning]

I think that is not very efficient to make one useEffect trigger the other but maybe I am not seeing the whole picture

Related