Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks useEffect only on update?

If we want to restrict useEffect to run only when the component mounts, we can add second parameter of useEffect with [].

useEffect(() => {
  // ...
}, []);

But how can we make useEffect to run only when the moment when the component is updated except initial mount?

like image 635
koo Avatar asked Mar 09 '19 08:03

koo


People also ask

How do I run only useEffect on update?

Use the Cleanup function of the useEffect without using an empty array as a second parameter: useEffect(() => { return () => { // your code to be run on update only. } }); You can use another useEffect (with an empty array as a second parameter) for initial mount, where you place your code in its main function.

Can you make useEffect hook to run only on component update and not when component mounted state is true or false?

If you want the useEffect to run only on updates except initial mount, you can make use of useRef to keep track of initialMount with useEffect without the second parameter.

Does useEffect run after update?

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.

Does useEffect always run after render?

useEffect has deps of [] so this only happens on the first render only. Then you are changing state 2 times, so a re-render happens.


Video Answer


4 Answers

If you want the useEffect to run only on updates except initial mount, you can make use of useRef to keep track of initialMount with useEffect without the second parameter.

const isInitialMount = useRef(true);

useEffect(() => {
  if (isInitialMount.current) {
     isInitialMount.current = false;
  } else {
      // Your useEffect code here to be run on update
  }
});
like image 92
Shubham Khatri Avatar answered Oct 19 '22 08:10

Shubham Khatri


I really like Shubham's response, so I made it a custom Hook

/**
 * A custom useEffect hook that only triggers on updates, not on initial mount
 * @param {Function} effect
 * @param {Array<any>} dependencies
 */
export default function useUpdateEffect(effect, dependencies = []) {
  const isInitialMount = useRef(true);

  useEffect(() => {
    if (isInitialMount.current) {
      isInitialMount.current = false;
    } else {
      return effect();
    }
  }, dependencies);
}
like image 45
Mario Campa Avatar answered Oct 19 '22 09:10

Mario Campa


Both Shubham and Mario suggest the right approach, however the code is still incomplete and does not consider following cases.

  1. If the component unmounts, it should reset it's flag
  2. The passing effect function may have a cleanup function returned from it, that would never get called

Sharing below a more complete code which covers above two missing cases:

import React from 'react';

const useIsMounted = function useIsMounted() {
  const isMounted = React.useRef(false);

  React.useEffect(function setIsMounted() {
    isMounted.current = true;

    return function cleanupSetIsMounted() {
      isMounted.current = false;
    };
  }, []);

  return isMounted;
};

const useUpdateEffect = function useUpdateEffect(effect, dependencies) {
  const isMounted = useIsMounted();
  const isInitialMount = React.useRef(true);

  React.useEffect(() => {
    let effectCleanupFunc = function noop() {};

    if (isInitialMount.current) {
      isInitialMount.current = false;
    } else {
      effectCleanupFunc = effect() || effectCleanupFunc;
    }
    return () => {
      effectCleanupFunc();
      if (!isMounted.current) {
        isInitialMount.current = true;
      }
    };
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
like image 10
Sankalp Lakhina Avatar answered Oct 19 '22 07:10

Sankalp Lakhina


You can get around it by setting the state to a non-boolean initial value (like a null value) :

  const [isCartOpen,setCartOpen] = useState(null);
  const [checkout,setCheckout] = useState({});

  useEffect(() => {

    // check to see if its the initial state
    if( isCartOpen === null ){

      // first load, set cart to real initial state, after load
      setCartOpen( false );
    }else if(isCartOpen === false){

      // normal on update callback logic
      setCartOpen( true );
    }
  }, [checkout]);
like image 5
awongh Avatar answered Oct 19 '22 07:10

awongh