React hooks useEffect only on update?
ReactjsReact HooksReactjs Problem Overview
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?
Reactjs Solutions
Solution 1 - Reactjs
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 - Reactjs
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 - Reactjs
Both Shubham and Mario suggest the right approach, however the code is still incomplete and does not consider following cases.
- If the component unmounts, it should reset it's flag
- 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 - Reactjs
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 - Reactjs
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 - Reactjs
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 - Reactjs
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>;
}
Solution 8 - Reactjs
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.