Consider the following example:
const userRole = sessionStorage.getItem('role');
const { data, setData, type, setTableType } = useTable([]);
useEffect(() => {
const getData = async () => {
// fetch some data from API
const fetchedData = await axios('..');
if (userRole === 'admin') {
setData([...fetchedData, { orders: [] }]);
} else {
setData(fetchedData);
}
if (type === '1') {
setTableType('normal');
}
};
getData();
}, []);
I want to run this effect ONLY on mounting and that's it, I don't care whether the userRole
or setData
has changed or not!
So, Now my questions are:
userRole
being missing in the
dependencies array? Its not even a state!setData
being missing in the dependencies array
isn't it will always be the same reference or does it change?// eslint-disable-line react-hooks/exhaustive-deps
line? Or this is absolutely barbaric?EDIT:
Let me rephrase the question What If these variables change and I don't care about their new values? Especially when type
is getting updated in different places in the same file. I just want to read the initial value and run useEffect once how to achieve that?
react-hooks/exhaustive-deps warning
Side Effect Runs Only Once After Initial Render You can pass an empty array as the second argument to the useEffect hook to tackle this use case. useEffect(() => { // Side Effect }, []); In this case, the side effect runs only once after the initial render of the component.
The warning "React Hook useEffect has a missing dependency" occurs when the useEffect hook makes use of a variable or function that we haven't included in its dependencies array. To solve the error, disable the rule for a line or move the variable inside the useEffect hook.
What happens if you don't specify the dependencies list in useEffect? If you don't specify it, the effect runs after each render. If it's empty ( [] ), the effect runs once, after the initial render. It must — or as we'll see later, should — contain the list of values used in the effect.
Multiple dependencies log(“This useEffect will run either data or siteUrl changes”, data, siteUrl); }, [data, siteUrl]); In the above scenario, useEffect will run when either value of 'data' or 'siteUrl' changes. We can also give more dependencies according to our requirements.
Edit: After an update to React, it seems Eslint has begun complaining about the below solution, so I'd probably use // eslint-disable-line
Note: This answer is not written with React 18's double useEffect in mind.
You're correct in that giving useEffect
an empty dependencies array is the way to go if you want the effect to run only once when the component mounts. The issue that ESLint is warning you about is the potential for the effect to execute using stale data, since it references external state properties, but as you've noticed, giving the array the dependencies it asks for causes the effect to run whenever any of them changes as well.
Luckily there is a simple solution that I'm surprised hasn't been yet mentioned — wrap the effect in a useCallback
. You can give the dependencies to useCallback
safely without it executing again.
// Some state value
const [state, setState] = useState();
const init = useCallback(
() => {
// Do something which references the state
if (state === null) {}
},
// Pass the dependency to the array as normal
[state]
);
// Now do the effect without any dependencies
useEffect(init, []);
Now init
will re-memoize when a dependency changes, but unless you call it in other places, it will only actually be called for execution in the useEffect
below.
To answer your specific questions:
userRole
because React components re-run for every render, meaning userRole
could have a different value in the future. You can avoid this by moving userRole
outside your function.setData
may realistically always the same, the potential for it to change exists, which is why ESLint wants you to include it as a dependency. Since it is part of a custom hook, this one cannot be moved outside your function, and you should probably include it in the dependencies array.Linting is a process of analyzing code for potential errors. Now when we talk about why do we get a lint error, we need to understand that the rules were set by keeping in mind the ideal use cases of particular functionality.
Here incase of a useEffect hook, the general notion says that if we have a value which might change or which might lead to a change in logic flow, all those should go into the dependencies array.
So, data is the first candidate to go in. Similar is the case with userRole as it is being used to control the logic flow and not simply as a value.
Going with linter suggestion to ignore the error is what I recommend.
useEffect
thus it's a dependency (if it will change - the useEffect
is invalid)useEffect
doesn't know if it will be the same or not, that's why it's asking for a dependencyconst userRole = sessionStorage.getItem('role');
const { data, setData } = useTable([]);
useEffect(() => {
const getData = async () => {
// fetch some data from API
const fetchedData = await axios('..');
if (userRole === 'admin') {
setData([...fetchedData, { orders: [] }]);
} else {
setData(fetchedData);
}
};
getData();
}, [userRole, setData]);
“But I only want to run it on mount!”, you’ll say. For now, remember: if you specify deps, all values from inside your component that are used by the effect must be there. Including props, state, functions — anything in your component.
Another solution is to create a hook for initializing (running once).
import { useState } from 'react';
export const useInit = initCallback => {
const [initialized, setInitialized] = useState(false);
if (!initialized) {
initCallback();
setInitialized(true);
}
};
Then use it on you React components:
useInit(() => {
// Your code here will be run only once
});
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