'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?



Solution 1:[1]

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
  }
});

Solution 2:[2]

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);
}

Solution 3:[3]

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
};

Solution 4:[4]

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]);

Solution 5:[5]

Took help from Subham's answer This code will only run for particular item update not on every update and not on component initial mounting.

const isInitialMount = useRef(true);    //useEffect to run only on updates except initial mount


//useEffect to run only on updates except initial mount
  useEffect(() => {
    if (isInitialMount.current) {
        isInitialMount.current = false;
     } else {              
         if(fromScreen!='ht1' && appStatus && timeStamp){
            // let timeSpentBG = moment().diff(timeStamp, "seconds");
            // let newHeatingTimer = ((bottomTab1Timer > timeSpentBG) ? (bottomTab1Timer - timeSpentBG) : 0);
            // dispatch({
            //     type: types.FT_BOTTOM_TAB_1,
            //     payload: newHeatingTimer,
            // })
            // console.log('Appstaatus', appStatus, timeSpentBG, newHeatingTimer)
         }
     }
  }, [appStatus])

Solution 6:[6]

Shorter One

const [mounted, setMounted] = useRef(false)

useEffect(() => {
  if(!mounted) return setMounted(true)
  ...
})

React Hook Solution

Hook

export const useMounted = () => {
  const mounted = useRef(false)

  useEffect(() => {
    mounted.current = true
    return () => {
      mounted.current = false
    }
  }, [])

  return () => mounted.current
}

Usage

const Component = () => {
  const mounted = useMounted()

  useEffect(() => {
    if(!mounted()) return
    ...
  })
}

Solution 7:[7]

To make a custom hook compliant with the rules of hooks you don't need to actually pass dependencies, just wrap your effect function with useCallback

function useEffectOnUpdate(callback) {
  const mounted = useRef();

  useEffect(() => {
    if (!mounted.current) {
      mounted.current = true;
    } else {
      callback();
    }
  }, [callback]);
};

function SomeComponent({ someProp }) {
  useEffectOnUpdate(useCallback(() => {
    console.log(someProp);
  }, [someProp]));

  return <div>sample text</div>;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Joel 8 bitar
Solution 2
Solution 3
Solution 4 awongh
Solution 5 Ajmal Hasan
Solution 6
Solution 7 ?????? ????????