120

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?

2

10 Answers 10

244

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 } }); 
Sign up to request clarification or add additional context in comments.

5 Comments

I thought that useRef can only be used to DOM manipulation. thanks!
In fact, this question is listed in React FAQ and here here it explicitly says that this is the right way.
This is it. I suggest understanding useRef first and then use useUpdateEffect from react-use.
I think strict mode breaks this because of the double render - codesandbox.io/s/sharp-feynman-9qs09s
FWIW Using a mounting check, e.g. the React ref, is now generally considered anti-pattern. Just return a cleanup function to properly handle and cleanup any effects when the component unmounts.
51

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

5 Comments

Why are you disabling the eslint rule?
I couldn't find a way to make it compliant with the rule, so I disabled it. I will remove it from the answer since is not relevant.
For typescript I had to change to ` useUpdateEffect(effect: Function, dependencies: any[] = [])`
Shouldn't we return effect(); instead of just calling effect(); so that we honor the clean up function?
Note that when the development build of react is being used, react strictmode will call the useEffect twice on the initial mount, thus making the useUpdateEffect run when the component mounts.
9

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

2 Comments

Great. Why did we use useIsMounted()? Why shouldn't we use if (!isInitialMount.current) while cleaning up?
Why do we need to set isInitialMount.current = true; when cleaning up? Is that really necessary?
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 ... }) } 

1 Comment

Isn't the return used for the cleanup on unmount?
5

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

1 Comment

This code won't work because isCartOpen isn't being observed and will always be null.
4

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

Comments

3

If you tried Shubham's answer, and the useeffect is still being called on the initial mount, you can easily fix this by disabling React strictmode. But if you don't want to disable strictmode, use this.

// The init variable is necessary if your state is an object/array, because the == operator compares the references, not the actual values. const init = []; const [state, setState] = useState(init); const dummyState = useRef(init); useEffect(() => { // Compare the old state with the new state if (dummyState.current == state) { // This means that the component is mounting } else { // This means that the component updated. dummyState.current = state; } }, [state]); 

Works in development mode...

function App() { const init = []; const [state, setState] = React.useState(init); const dummyState = React.useRef(init); React.useEffect(() => { if (dummyState.current == state) { console.log('mount'); } else { console.log('update'); dummyState.current = state; } }, [state]); return ( <button onClick={() => setState([...state, Math.random()])}>Update state </button> ); } ReactDOM.createRoot(document.getElementById("app")).render( <React.StrictMode> <App /> </React.StrictMode> );
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="app"></div>

And in production.

function App() { const init = []; const [state, setState] = React.useState(init); const dummyState = React.useRef(init); React.useEffect(() => { if (dummyState.current == state) { console.log('mount'); } else { console.log('update'); dummyState.current = state; } }, [state]); return ( <button onClick={() => setState([...state, Math.random()])}>Update state </button> ); } ReactDOM.createRoot(document.getElementById("app")).render( <React.StrictMode> <App /> </React.StrictMode> );
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <div id="app"></div>

Comments

1

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

Comments

1

When using React strict mode, the solution has to change because React will do the initial render twice and throw an exception if the order of your use of useRef and useEffect is different. I found that the following works as a modification of @shubham-khatri's answer:

const isInitialMount = useRef(true); useEffect(() => { if (isInitialMount.current === true) { // Your useEffect code here to be run on update } isInitialMount.current = false; }); 

1 Comment

Sounds like that would cause it to only run at initial mount?
-3

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.

2 Comments

I'm getting infinite looping
The react cleanup function is executed when component unmounts. The OP is asking how to execute code on "update", or in other words, on every render except the initial render, which is run when mounting.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.