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?
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.
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 every render? Yes! By default, it runs both after the first render and after every update.
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.
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
}
});
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);
}
Both Shubham and Mario suggest the right approach, however the code is still incomplete and does not consider following cases.
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
};
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]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With