I'm making a React App and I'm getting this warning. I'm trying to make two API calls when the component mounts:
useEffect(() => {
getWebsites();
loadUserRatings();
}, []);
That's why I have an empty array, because I want to call it just once when the component gets mounted. But I'm still getting the warning, how can I fix it?
Those two functions are passed to the component using connect from react redux, the whole component looks like this:
const Wrapper = (props) => {
const { getWebsites, loadUserRatings } = props;
useEffect(() => {
getWebsites();
loadUserRatings();
}, []);
return (
<>
<Header />
<Websites />
<Sync />
</>
);
};
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.
Empty dependency array So what happens when the dependency array is empty? It simply means that the hook will only trigger once when the component is first rendered. So for example, for useEffect it means the callback will run once at the beginning of the lifecycle of the component and never again.
Missing dependencies are those dependencies that are not available in the repository, so you cannot add them to your deployment set. You can set Deployer to ignore missing dependencies when you create the project (see Creating a Project) or when you check unresolved dependencies.
The "react-hooks/exhaustive-deps" rule warns us when we have a missing dependency in an effect hook. To get rid of the warning, move the function or variable declaration inside of the useEffect hook, memoize arrays and objects that change on every render or disable the rule.
TL;DR
You should add getWebsites
, loadUserRatings
to the dependencies array.
useEffect(() => {
getWebsites();
loadUserRatings();
}, [getWebsites, loadUserRatings]);
React asks you to add variables (whose values may change) to the dependency array so that the callback can use updated values of those variables.
You might face an issue where the useEffect
's callback runs more times than intended. Consider the below example:
const Bla = (props) => {
const foo = () => {
console.log('foo')
}
useEffect(() => {
foo();
}, [foo])
return <span>bla</span>
}
As foo
is used inside useEffect
's callback, it is added to the dependencies. But every time the component Bla
renders, a new function is created and foo
changes. This will trigger the useEffect
's callback.
This can be fixed using the hook useCallback
:
const foo = useCallback(() => {
console.log('foo');
}, []);
Now, when Bla
rerenders, a new function will still be created but useCallback
will make sure that foo
doesn't change (memoization) which helps in preventing useEffect
's callback from running again.
Note: If there are variables that are used inside foo
that change over time, they should be added to the useCallback
's dependencies array so that the function uses updated values.
You have to add getWebsites
and loadUserRatings
to the dependencies of useEffect
:
useEffect(() => {
getWebsites();
loadUserRatings();
}, [getWebsites, loadUserRatings]
All variables defined outside the useEffect
hook need to be added, with the stipulation that they're defined in the body of the component or custom hook the useEffect() is called from, or passed down from parameters. Variables defined outside of the component or custom hook don't need to be added to the dependencies list. (memoization) It's one of the lesser known rules of hooks.
Note: (this doesn't work for your scenario as your functions are passed down through the props) You can also wrap your function in a useCallback
hook or define the variables you need inside the useEffect
hook itself if you don't want to add it to the dependencies of your useEffect
hook.
In my experience, it's not typical to pass un-memoized functions down via props
like this.
// don't do this
<Wrapper
getWebsites={() => fetchJson('websites').then(setWebsites)}
loadUserRatings={() => fetchJson('ratings').then(setUserRatings)}
/>
If they are correctly memoized (using a hook like useCallback()
, or by being defined outside of any component), then it's safe to pass them to the deps
of your useEffect()
without any difference in behavior. Here's an example that fixes the scenario above.*
// do this
const fetchJson = (...args) => fetch(...args).then(res => res.json());
const Parent = () => {
const [websites, setWebsites] = useState([]);
const [userRatings, setUserRatings] = useState({});
// useCallback creates a memoized reference
const getWebsites = useCallback(
() => fetchJson('websites').then(setWebsites),
[setWebsites]
);
const loadUserRatings = useCallback(
() => fetchJson('ratings').then(setUserRatings),
[setUserRatings]
);
...
<Wrapper
getWebsites={getWebsites}
loadUserRatings={loadUserRatings}
/>
* useState()
memoizes the dispatch function in its return value, so technically it would be safe to pass []
as the deps
to each useCallback()
here, but I believe that specifying the dispatch functions as dependencies helps improve clarity by explicitly communicating the author's intent, and there's no disadvantage to passing them.
Ramesh's answer is sufficient for this situation.
If you find that you're stuck with the first scenario, then, as a last resort, you can initialize props
into your component's state like this.
const Wrapper = (props) => {
const [{ getWebsites, loadUserRatings }] = useState(props);
useEffect(() => {
getWebsites();
loadUserRatings();
}, [getWebsites, loadUserRatings]);
return (
<>
<Header />
<Websites />
<Sync />
</>
);
};
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