I only want useEffect to run when my dependency list changes, it is also running every time the component is mounted, is there any way to not fire on mount?
You can tell React to skip applying an effect if certain values haven’t changed between re-renders.
I initially thought that meant it shouldn't re-render on subsequent mounts but this question cleared that up.
I am displaying a list of records in a master "page" (react-router), the user can choose a single record and go to the detail page, and then return to the master page - so the master list component is completely unmounted/mounted in that scenario. And every time I load the "master page", I see the data being fetched, I only want this to happen when one of the dependencies changes; these dependencies and the data itself are stored in Redux so they're global.
Can useEffect or another hook be made to only fire when the dependencies change?
const {page, pageSize, search, sorts} = useSelector(getFilters);
const data = useSelector(getData);
useEffect(() => {
console.log("fetching");
dispatch(fetchData(page, pageSize, search, sorts));
}, [page, pageSize, search, sorts]);
Important: the useEffect hook will always run on mount regardless of if there is anything in its dependency array. We probably don't want to actually run this effect on our data when it's undefined (as it will be on initial render) but rather we want to wait until it is populated from the API call.
We can make the React useEffect callback not run on the first render by creating a ref that keeps track of whether the first render is done. Then we can check the ref's value to see when the first render is done and run the function we want when the first render is done.
You can't configure it out of the box.
But, a common pattern is to use some isMounted
flag like so:
// Is Mounted
const useFetchNotOnMount = () => {
...
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) {
console.log('fetching');
dispatch(fetchData(filters));
} else {
isMounted.current = true;
}
}, [dispatch, filters]);
};
// Same (Is First Render)
const useFetchNotOnMount = () => {
...
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log("fetching");
dispatch(fetchData(filters));
}
}, [dispatch, filters]);
};
useEffect
If you have several useEffect
to prevent from running at initially, you can do the following:
export default function App() {
const mountedRef = useMountedRef();
const [isLoggedIn, setLoggedIn] = React.useState(false);
const [anotherOne, setAnotherOne] = React.useState(false);
React.useEffect(() => {
if (mountedRef.current) {
console.log("triggered", isLoggedIn);
}
}, [isLoggedIn]);
React.useEffect(() => {
if (mountedRef.current) {
console.log("triggered", anotherOne);
}
}, [anotherOne]);
React.useEffect(() => {
if (mountedRef.current) {
console.log("triggered", isLoggedIn, anotherOne);
}
}, [anotherOne, isLoggedIn]);
return (
<div>
<button onClick={() => setLoggedIn(true)}>Login</button>
</div>
);
}
const useMountedRef = () => {
const mountedRef = React.useRef(false);
React.useEffect(() => {
setTimeout(() => {
mountedRef.current = true;
});
}, []);
return mountedRef;
};
Demo: https://stackblitz.com/edit/react-eelqp2
One thing important is that you have to use setTimeout
to make a reasonable delay to make sure that the ref value is set to true after all initial useEffects.
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