仅对更新有效?

如果我们想要限制 useEffect只在组件安装时运行,我们可以添加 useEffect的第二个参数与 []

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

但是,除了初始挂载之外,我们如何才能使 useEffect仅在组件更新的时刻运行呢?

68572 次浏览

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.

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

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

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

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


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.

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