3

I'm using multiple useEffect hooks to perform the componentDidMount and componentDidUpdate functionalities, however, looks like when component loads, all my useEffect fires initially...

const testComp = () => {
   const stateUserId = useSelector(state => { return state.userId; });
   const [userId, setUserId] = useState(stateUserId);
   const [active, setActive] = useState(false);
   const [accountId, setAccountId] = useState();
   
   useEffect(() => {
      console.log('component did mount...');
   }, [userId]);
   
   useEffect(() => {
      console.log('should trigger when userId changes...');
   }, [userId]);

   useEffect(() => {
      console.log('should trigger when active changes...');
   }, [active]);
   
   useEffect(() => {
      console.log('should trigger when accountId changes...');
   }, [accounted]);
  
   return (<div>...</div);
}

when my component mounts, I see all those console log there

component did mount...
should trigger when userId changes...
should trigger when active changes...
should trigger when accountId changes...

How could I only let my first useEffect fires, but the other three fires when the dependency changes only?

Drex
  • 3,346
  • 9
  • 33
  • 58
  • 1
    useEffect will always execute at least on mount, no matter what you pass in the dependency array. You should create a custom hook like useUpdateEffect. This question shows how https://stackoverflow.com/questions/55075604/react-hooks-useeffect-only-on-update – Rodrigo Amaral Aug 01 '20 at 03:23
  • @Rodrigi Amara, thanks for sharing the idea, I'm wondering if that is the case, why not perform in the same main component by using the useRef flag but using a custom hook? In addition, how could I call the custom hook from my main component? – Drex Aug 01 '20 at 03:30
  • 1
    Custom hooks are just functions. You can create a folder just for hooks and import them into your components. Or you could use useRef inside your component (like the most voted answer) does in the question, but you won't be able to reuse this logic outside this component. BTW, it's in the FAQ https://reactjs.org/docs/hooks-faq.html#can-i-run-an-effect-only-on-updates – Rodrigo Amaral Aug 01 '20 at 03:35
  • All effects run at least one time there are some ref tricks to skip some. – Ioannis Potouridis Aug 01 '20 at 03:55

3 Answers3

4

useEffect is not a direct replacement of componentDidMount and componentDidUpdate. Effect will run after each render, which is why you are seeing all those console logs. According to the React documentation, the second parameter of useEffect means

you can choose to fire them (effects) only when certain values have changed.

After the initial render, if the component is rendered again, only effects watch the corresponding value changes are triggered.

One way to achieve what you want is by creating additional variables to host initial values and do comparisons in the useEffect when you need to.

const testComp = () => {
  const initUserId =  useSelector(state => { return state.userId; });
  const stateUserId = initUserId;
   const [userId, setUserId] = useState(stateUserId);
   
   useEffect(() => {
      console.log('component did mount...');
   }, [userId]);
   
   useEffect(() => {
      if (userId !== initUserId) {
        console.log('should trigger when userId changes...');
      }
      
   }, [userId]);
  
   return <div>...</div>
}
Andrew Zheng
  • 2,612
  • 1
  • 20
  • 25
  • Yeah, that'd work, however, seems like I need to create a local state variable for each field that's needed in every. useEffect dependency list – Drex Aug 01 '20 at 03:49
  • @KevDing There is an answer with custom useDidMountEffect hooks if you don't want to create local variables. https://stackoverflow.com/questions/53253940/make-react-useeffect-hook-not-run-on-initial-render – Andrew Zheng Aug 01 '20 at 03:58
  • Thanks for sharing, I am looking at the custom hook solution, but got a little confused there. if my deps changes the first time from parent component, how could the func() be fired? Because at this time the `didMount.current` value is still false...and resetting the value to true won't trigger another useEffect because it's a useRef...Is there any way to set it true from my parent component so the custom hook can execute the func()? – Drex Aug 01 '20 at 04:06
  • On the initial render, useDidMountEffect will be triggered and change `didMount.current` to `true`. That's why when your deps changes the first time (noted that this is the second render & first dep value change), `didMount.current` is already true at that point. So `func()` will be exectued – Andrew Zheng Aug 01 '20 at 04:24
1

Any useEffect hook will always be fired at component mount and when some of the variables in its dependency array changes. If you want to perform an action just when the variable changes you must check the incoming value first and do the required validations. Also your first useEffect must have an empty dependency array to fire it just when the component mounts, because as it is it will also be called when userId changes.

MaCadiz
  • 1,699
  • 9
  • 11
1

you can use custom useEffect:

const useCustomEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        if (didMount.current) func();
        else didMount.current = true;
    }, deps);
}

and use it in your component:


useCustomEffect(() => {
      console.log('trigger when userId changes...');
}, [userId]);

  • Thanks for sharing, if my deps changes the first time, how the func() being fired at that time? Because the didMount.current is still false...and resetting the value to true won't trigger another useEffect...Is there any way to set it true from my component? – Drex Aug 01 '20 at 03:57
  • For more information, refer to this link: https://stackoverflow.com/a/57941438/10252964 – Mohsen Niknam Pirzadeh Aug 01 '20 at 04:14