How to fix – react hook useEffect has missing dependencies?
Facebook developed ReactJS as an open-source Javascript hook library for building fast and engaging web apps. Since then, React’s popularity has been on the rise with more developers liking it.
At first, components could be made only using classes. Then, hooks were introduced in React 16.8 as a breaking change which allowed developers to use state and other React features without having to write a class. In the years since then, hooks have been fairly established. With useState()
and useEffect()
being the most popular hooks, developers have gotten pretty comfortable with how they work and what they can do.
However, it is likely if we have ever used useEffect
hook, we probably encountered the following ESlint warning in our developer life:
React Hook useEffect has a missing dependency: '[DEPENDENCY_NAME]'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Code language: PHP (php)
So, what is this warning, and what can be done to fix it? Let’s dig deeper into it.
Prerequisites
Here are some prerequisites to be aware of before we get started:
- Knowledge and some experience with class-based and functional-based components in React.
- Familiarity with the syntax of JSX language used in React.
- The use of React hooks is generally understood.
- Some knowledge of the dependency list in
useEffect
hook is an advantage.
Introduction
To fully understand why this warning comes in the first place, let’s take a break to understand why we use the useEffect
hook which needs a shift in our focus from object-oriented programming to the concept of “pure functions” in functional programming.
Pure Functions
A pure function returns the same value for the same arguments without any side-effects (no change in local static variables, non-local variables or mutable references arguments).
As far as React goes, a purely functional component is one that always outputs the same HTML for the same props (arguments) it receives without maintaining any internal state or interacting/modifying any external variables. This is acceptable for static components, however, in real-world settings, we may need to maintain internal states or load real-time data which is what react hooks do.
UseEffect Hook
The Effect hook especially lets us handle those “side effects” in function components. A side effect is simply anything that impacts something outside the scope of the current function like API requests, updating DOM elements, etc. Inside, it usually takes a callback function which always gets called on every render. But, it can also take an array of dependencies as a second argument. This means that the callback function will also run if any of those dependencies change since the last render.
We can use an empty dependency array if we wish to run the effect only once on every render. However, this dependency list can often lead to problems. We get the warning “React Hook useEffect has a missing dependency” when useEffect depends on a variable or function that we didn’t include in its dependencies.
Recreating the Scenario
Let’s try to recreate a simple scene where we can get the warning. We are trying to build a React app and in one of the components, we are getting a user object and want to fetch some details of the user to show it in the browser every time the component renders.
const UserCard = (user) => {
const [userDetails, setUserDetails] = useState(null);
useEffect(() => {
const response = axios.get('/dummy-api', user);
setUserDetails(response);
}, []);
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
}
Code language: JavaScript (javascript)
This is where we will receive an ESlint warning:
React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array.
Code language: PHP (php)
So, why is it occurring? Because the useEffect
code relies on the ‘user’ object to make the API call. But, when the component re-renders, the value of ‘user’ may no longer be accurate. As the effect only ran once, the user will only be set the first time component is rendered.
Although, the code will work as expected, not including the right dependencies may lead to the risk of introducing future bugs which can be extremely difficult to fix.
Infinite Re-rendering Problem
So, the first solution that comes to mind is to provide the dependency that useEffect
wants, and everything will be fine.
...
...
useEffect(() => {
const response = axios.get('/dummy-api', user);
setUserDetails(response);
}, [user]); //adding the missing dependency
...
...
Code language: JavaScript (javascript)
We will, however, find an infinite re-rendering problem. It is because the useEffect
won’t stop executing because of constantly changing dependencies even if the user remains the same.
But how is it possible? Despite the fact that we are passing the same object with the same key and value pairs, the object itself is not the same. When we re-render our component, we create an entirely new object, then pass it to our component. And we need to remember two things here:
- All Objects and Arrays are compared by reference in JavaScript.
- JavaScript doesn’t handle object equality. For Eg:
if ( { a: 1, b: 2 } === { a: 1, b: 2} ) {
console.log("Equal");
} else {
console.log("Not equal");
}
// Output: Not equal
Code language: JavaScript (javascript)
So, useEffect
checks if anything has changed in the dependencies and due to the different references between the two objects, both users will never be equal and callback inside runs. The component re-renders with every state update, and this process continues indefinitely. So, how can we solve this serious problem?
Fixing the Issue
The first thing to do in these kinds of cases analyzing what we’re doing vs what we want to accomplish. As in the above example, would it be better if we try to move the logic of API calling to some parent component and pass the data down as props?
It is often possible to avoid problems like this by taking some time to step back and get some perspective. If there’s nothing you can do, let’s look at some of the viable fixes we have then.
Solution 1: Passing a stable reference to useEffect
If the user object declaration had been inside the same component, we could have passed it inside the useEffect
hook itself. This would remove the warning because the hook no longer had a dependency on the object because it was declared inside of it.
const UserCard = () => {
const [userDetails, setUserDetails] = useState(null);
useEffect(() => {
<em>// ?️ move object / array / function declaration inside of the useEffect hook
const user = {id: "[hexValue]", token: "[userToken]"}</em>
const response = axios.get('/dummy-api', user);
setUserDetails(response);
}, []);
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
Code language: PHP (php)
Alternatively, we can also move the declaration of the function or variable outside our component.
// ?️ <em>move function/variable declaration outside of component</em>
<em>const user = {id: "[hexValue]", token: "[userToken]"}</em>
const UserCard = () => {
const [userDetails, setUserDetails] = useState(null);
useEffect(() => {
const response = axios.get('/dummy-api', user);
setUserDetails(response);
}, []);
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
Code language: HTML, XML (xml)
Solution 2: Get memoized value/function with the useMemo/useCallback hook
This can be one of the recommended solutions as it is possible to get a memoized value that is not affected by renders by using the useMemo
hook.
As parameters, useMemo
takes a function that returns a memoized value and an array of dependencies. We can think of memoizing as caching a value so that it doesn’t have to be recalculated. The function runs only when a dependency updates.
<em>// const user = {id: "[hexValue]", token: "[userToken]"}</em>
const UserCard = (user) => {
const [userDetails, setUserDetails] = useState(null);
<em>// ?️ get memoized value
const userObject = useMemo(() => {
return {user: user};
}, [user]);</em>
useEffect(() => {
const response = axios.get('/dummy-api', userObject);
setUserDetails(response);
}, [<em>userObject</em>]); <em>// ?️ safely include in dependencies array</em>
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
Code language: JavaScript (javascript)
In order to get a memoized callback for functions, we can use the useCallback
hook. It is similar to useMemo
hook, but usecallback
returns a memoized function rather than a memoized value.
<em>// const user = {id: "[hexValue]", token: "[userToken]"}</em>
const UserCard = (user) => {
const [userDetails, setUserDetails] = useState(null);
<em>// ?️ get memoized callback</em>
const splitName = useCallback((name) => {
return name.split(" ");
}, [user]);
<em>// ?️ get memoized value</em>
const userObject = useMemo(() => {
return {user: user};
}, [user]);
useEffect(() => {
const response = axios.get('/dummy-api', userObject);
const updatedName = splitName(response.name);
setUserDetails({ ...response, name: updatedName});
}, [<em>userObject</em>, splitName]); <em>// ?️ safely include in dependencies array</em>
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
Code language: JavaScript (javascript)
Solution 3: Track Component render using useRef()
Using useRef
hooks, we can store mutable values that don’t cause a re-render when they are modified.
We can use a ref to keep track of whether a render has occurred and API is fetched or not. Initially, we set its value to false and check inside useEffect
if the .current property of our ref is false, then we can run the code inside and set our user object, otherwise, simply skip it.
const UserCard = (user) => {
const [userDetails, setUserDetails] = useState(null);
<em>const fetchingComplete = useRef(false);</em>
useEffect(() => {
<em>if(fetchingComplete.current === false){</em> <em>//if first time rende</em>r
const response = axios.get('/dummy-api', user);
setUserDetails(response);
<em>fetchingComplete.current === true; //change the current ref value without causing a re-render
}</em>
}, [user]);
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
}
Code language: JavaScript (javascript)
If we also want to fetch the data every time user value changes, we can create a similar ref to keep track of previous data. Inside the useEffect
, we can do a deep comparison between the user ref object and the current user object. If they are different, then there is a change in the user. So, we can run the code inside and set the updated data to cause a re-render.
const UserCard = (user) => {
const [userDetails, setUserDetails] = useState(null);
<em>const prevUser = useRef(user);</em>
useEffect(() => {
<em>if(deep_comparator_func(fetchingComplete.current, user)){</em>
const response = axios.get('/dummy-api', user);
setUserDetails(response);
<em>fetchingComplete.current === user;
}</em>
}, [user]);
return (
<div>
<p>Name: {userDetails.name}</p>
<p>Country: {userDetails.country}</p>
<p>Email: {userDetails.email}</p>
</div>
);
}
Code language: HTML, XML (xml)
Solution 4: Leave a comment to silence the warning
One of the least recommended solutions to fix “React Hook useEffect has a missing dependency” is to remove the dependencies and avoid the warning by silencing the ESline rule with the below comment. But it means we are kind of lying to React that the effect is independent of any variables or objects. This works for now but may cause issues in the future.
...
...
useEffect(() => {
const response = axios.get('/dummy-api', user);
setUserDetails(res);
<em>// eslint-disable-next-line react-hooks/exhaustive-deps</em>
}, []);
...
...
Code language: JavaScript (javascript)
Conclusion
That was a pretty long explanation for such a small warning. But when it comes to hooks, quoting Abramov’s article: “Stop lying to React about dependencies”. Now, we understand there’s a lot going on under the hood even if it comes to a small warning. I hope you were able to gain some new knowledge and helped you improve as a developer.
If you ever feel lost and want help understanding where to start your coding journey then go check out Codedamn. There are amazing courses and blogs for web development and other coding concepts. Remember to practice coding on a daily basis. You can play around with your code on Codedamn’s online compiler as well
Let us know in the comments if you have any suggestions or queries. Finally, If you are interested in my content and would like to connect, then you can find me on Linkedin or Twitter.
Thank You for reading!
Sharing is caring
Did you like what Indrakant wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: