How to fix missing dependency warning when using useEffect React Hook
ReactjsEslintCreate React-AppReact HooksReactjs Problem Overview
With React 16.8.6 (it was good on previous version 16.8.3), I get this error when I attempt to prevent an infinite loop on a fetch request:
./src/components/BusinessesList.js
Line 51: React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array react-hooks/exhaustive-deps
I've been unable to find a solution that stops the infinite loop. I want to stay away from using useReducer()
. I did find this discussion [ESLint] Feedback for 'exhaustive-deps' lint rule #14920 where a possible solution is You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.
I'm not confident in what I'm doing, so I haven't tried implementing it just yet.
I have this current setup, https://stackoverflow.com/questions/53243203/react-hook-useeffect-runs-continuously-forever-infinite-loop and the only comment is about useCallback()
which I'm not familiar with.
How I'm currently using useEffect()
(which I only want to run once in the beginning similar to componentDidMount()
):
useEffect(() => {
fetchBusinesses();
}, []);
const fetchBusinesses = () => {
return fetch("theURL", {method: "GET"}
)
.then(res => normalizeResponseErrors(res))
.then(res => {
return res.json();
})
.then(rcvdBusinesses => {
// some stuff
})
.catch(err => {
// some error handling
});
};
Reactjs Solutions
Solution 1 - Reactjs
If you aren't using fetchBusinesses method anywhere apart from the effect, you could simply move it into the effect and avoid the warning
useEffect(() => {
const fetchBusinesses = () => {
return fetch("theURL", {method: "GET"}
)
.then(res => normalizeResponseErrors(res))
.then(res => {
return res.json();
})
.then(rcvdBusinesses => {
// some stuff
})
.catch(err => {
// some error handling
});
};
fetchBusinesses();
}, []);
If however you are using fetchBusinesses outside of render, you must note two things
- Is there any issue with you not passing
fetchBusinesses
as a method when it's used during mount with its enclosing closure? - Does your method depend on some variables which it receives from its enclosing closure? This is not the case for you.
- On every render, fetchBusinesses will be re-created and hence passing it to useEffect will cause issues. So first you must memoize fetchBusinesses if you were to pass it to the dependency array.
To sum it up I would say that if you are using fetchBusinesses
outside of useEffect
you can disable the rule using // eslint-disable-next-line react-hooks/exhaustive-deps
otherwise you can move the method inside of useEffect
To disable the rule you would write it like
useEffect(() => {
// other code
...
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Solution 2 - Reactjs
There are very good options for state management librariess if you are creating a new app or have enough flexibility. Check out Recoil.
Just for completeness:
1. (Stopped working) Use function as useEffect
callback
useEffect
callbackuseEffect(fetchBusinesses, [])
useEffect()
2. Declare function inside useEffect(() => {
function fetchBusinesses() {
...
}
fetchBusinesses()
}, [])
useCallback()
3. Memoize with In this case, if you have dependencies in your function, you will have to include them in the useCallback
dependencies array and this will trigger the useEffect
again if the function's params change. Besides, it is a lot of boilerplate... So just pass the function directly to useEffect
as in 1. useEffect(fetchBusinesses, [])
.
const fetchBusinesses = useCallback(() => {
...
}, [])
useEffect(() => {
fetchBusinesses()
}, [fetchBusinesses])
4. Function's default argument
As suggested by Behnam Azimi
> It's not best practice, but it could be useful in some cases.
useEffect((fetchBusinesses = fetchBusinesses) => {
fetchBusinesses();
}, []);
5. Create a custom hook
Create a custom hook and call it when you need to run function only once. It may be cleaner. You can also return a callback to reset re-run the "initialization" when need.
// customHooks.js
const useInit = (callback, ...args) => {
const [mounted, setMounted] = useState(false)
const resetInit = () => setMounted(false)
useEffect(() => {
if(!mounted) {
setMounted(true);
callback(...args);
}
},[mounted, callback]);
return [resetInit]
}
// Component.js
return ({ fetchBusiness, arg1, arg2, requiresRefetch }) => {
const [resetInit] = useInit(fetchBusiness, arg1, arg2)
useEffect(() => {
resetInit()
}, [requiresRefetch, resetInit]);
6. Disable eslint's warning
Disabling warnings should be your last resort, but when you do, better do it inline and explicitly, because future developers may be confused or create unexpected bugs without knowing linting is off
useEffect(() => {
fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
Solution 3 - Reactjs
./src/components/BusinessesList.js
Line 51: React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array react-hooks/exhaustive-deps
It's not a JavaScript/React error, but an ESLint (eslint-plugin-react-hooks) warning.
It's telling you that the hook depends on function fetchBusinesses
, so you should pass it as a dependency.
useEffect(() => {
fetchBusinesses();
}, [fetchBusinesses]);
It could result in invoking the function on every render if the function is declared in a component like:
const Component = () => {
/*...*/
// New function declaration every render
const fetchBusinesses = () => {
fetch('/api/businesses/')
.then(...)
}
useEffect(() => {
fetchBusinesses();
}, [fetchBusinesses]);
/*...*/
}
because every time the function is redeclared with a new reference.
The correct way of doing this stuff is:
const Component = () => {
/*...*/
// Keep the function reference
const fetchBusinesses = useCallback(() => {
fetch('/api/businesses/')
.then(...)
}, [/* Additional dependencies */])
useEffect(() => {
fetchBusinesses();
}, [fetchBusinesses]);
/*...*/
}
Or just define the function in useEffect
.
More: [ESLint] Feedback for 'exhaustive-deps' lint rule #14920
Solution 4 - Reactjs
These warnings are very helpful for finding components that do not update consistently: Is it safe to omit functions from the list of dependencies?.
However, if you want to remove the warnings throughout your project, you can add this to your ESLint configuration:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/exhaustive-deps": 0
}
}
Solution 5 - Reactjs
The solution is also given by React. They advice you use useCallback
which will return a memoize version of your function:
> The 'fetchBusinesses' function makes the dependencies of the useEffect Hook (at line NN) change on every render. To fix this, wrap the 'fetchBusinesses' definition into its own useCallback() Hook react-hooks/exhaustive-deps
useCallback
is simple to use as it has the same signature as useEffect
. The difference is that useCallback returns a function.
It would look like this:
const fetchBusinesses = useCallback( () => {
return fetch("theURL", {method: "GET"}
)
.then(() => { /* Some stuff */ })
.catch(() => { /* Some error handling */ })
}, [/* deps */])
// We have a first effect that uses fetchBusinesses
useEffect(() => {
// Do things and then fetchBusinesses
fetchBusinesses();
}, [fetchBusinesses]);
// We can have many effects that use fetchBusinesses
useEffect(() => {
// Do other things and then fetchBusinesses
fetchBusinesses();
}, [fetchBusinesses]);
Solution 6 - Reactjs
const [mount, setMount] = useState(false)
const fetchBusinesses = () => {
// Function definition
}
useEffect(() => {
if(!mount) {
setMount(true);
fetchBusinesses();
}
},[fetchBusinesses, mount]);
This is solution is pretty simple and you don't need to override ESLint warnings. Just maintain a flag to check whether the component is mounted or not.
Solution 7 - Reactjs
Just disable ESLint for the next line;
useEffect(() => {
fetchBusinesses();
// eslint-disable-next-line
}, []);
In this way, you are using it just like a component did mount (called once).
updated
or
const fetchBusinesses = useCallback(() => {
// Your logic in here
}, [someDeps])
useEffect(() => {
fetchBusinesses();
// No need to skip the ESLint warning
}, [fetchBusinesses]);
fetchBusinesses will be called every time someDeps changes.
Solution 8 - Reactjs
This article is a good primer on fetching data with hooks: https://www.robinwieruch.de/react-hooks-fetch-data/
Essentially, include the fetch function definition inside useEffect
:
useEffect(() => {
const fetchBusinesses = () => {
return fetch("theUrl"...
// ...your fetch implementation
);
}
fetchBusinesses();
}, []);
Solution 9 - Reactjs
Actually the warnings are very useful when you develop with hooks. But in some cases, it can needle you. Especially when you do not need to listen for dependencies change.
If you don't want to put fetchBusinesses
inside the hook's dependencies, you can simply pass it as an argument to the hook's callback and set the main fetchBusinesses
as the default value for it like this:
useEffect((fetchBusinesses = fetchBusinesses) => {
fetchBusinesses();
}, []);
It's not best practice, but it could be useful in some cases.
Also, as Shubham wrote, you can add the below code to tell ESLint to ignore the checking for your hook.
// eslint-disable-next-line react-hooks/exhaustive-deps
Solution 10 - Reactjs
You try this way:
const fetchBusinesses = () => {
return fetch("theURL", {method: "GET"})
.then(res => normalizeResponseErrors(res))
.then(res => {
return res.json();
})
.then(rcvdBusinesses => {
// Some stuff
})
.catch(err => {
// Some error handling
});
};
and
useEffect(() => {
fetchBusinesses();
});
It works for you.
But my suggestion is try this way and it also works for you. It's better than the previous way. I use it this way:
useEffect(() => {
const fetchBusinesses = () => {
return fetch("theURL", {method: "GET"})
.then(res => normalizeResponseErrors(res))
.then(res => {
return res.json();
})
.then(rcvdBusinesses => {
// Some stuff
})
.catch(err => {
// Some error handling
});
};
fetchBusinesses();
}, []);
If you get data on the base of a specific id, then add in callback useEffect [id]
. Then it cannot show you the warning
React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array
Solution 11 - Reactjs
You can remove the second argument type array []
, but the fetchBusinesses()
will also be called on every update. You can add an IF
statement into the fetchBusinesses()
implementation if you like.
React.useEffect(() => {
fetchBusinesses();
});
The other one is to implement the fetchBusinesses()
function outside your component. Just don't forget to pass any dependency arguments to your fetchBusinesses(dependency)
call, if any.
function fetchBusinesses (fetch) {
return fetch("theURL", { method: "GET" })
.then(res => normalizeResponseErrors(res))
.then(res => res.json())
.then(rcvdBusinesses => {
// some stuff
})
.catch(err => {
// some error handling
});
}
function YourComponent (props) {
const { fetch } = props;
React.useEffect(() => {
fetchBusinesses(fetch);
}, [fetch]);
// ...
}
Solution 12 - Reactjs
Well, if you want to look into this differently, you just need to know what options React has that are non exhaustive-deps
. One of the reason you should not use a closure function inside the effect is on every render, it will be recreated/destroyed again.
So there are multiple React methods in hooks that are considered stable and non-exhausted where you do not have to apply to the useEffect
dependencies, and in turn will not break the rules engagement of react-hooks/exhaustive-deps
. For example, the second return variable of useReducer
or useState
which is a function.
const [,dispatch] = useReducer(reducer, {});
useEffect(() => {
dispatch(); // Non-exhausted - ESLint won't nag about this
}, []);
So in turn, you can have all your external dependencies coexist with your current dependencies within your reducer function.
const [,dispatch] = useReducer((current, update) => {
const { foobar } = update;
// Logic
return { ...current, ...update };
}), {});
const [foobar, setFoobar] = useState(false);
useEffect(() => {
dispatch({ foobar }); // non-exhausted `dispatch` function
}, [foobar]);
Solution 13 - Reactjs
In my case, it had this warning with my local variable organization
, and when I put organization
in the dependency array, useEffect
would fetch infinitely. Therefore if you have some problems like mine, use useEffect
with the dependency array and split:
Because if you have multiple API calls that modify state, it invokes useEffect
multiple times.
From:
const { organization } = useSelector(withOrganization)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getOrganization({}))
dispatch(getSettings({}))
dispatch(getMembers({}))
}, [dispatch, organization])
To:
const { organization } = useSelector(withOrganization)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getOrganization({}))
dispatch(getSettings({}))
}, [dispatch, organization])
useEffect(() => {
dispatch(getMembers({}))
}, [dispatch])
Solution 14 - Reactjs
This warning happens, if variables that you are using inside useEffect
are defined inside the component or passed to the component as a prop. Since you defined fetchBusinesses()
inside same component, you have to pass it to the dependency array.
But if you imported fetchBusinesses()
and then used it inside useEffect
, you would not need to add it to the dependency array. That is how we actually set up our Redux applications: we always import our action creators and run it inside useEffect
without adding it to the dependency array.
The same is true for useMemo
too.
Solution 15 - Reactjs
You can get rid of this Es-lint warning by passing a reference to it:
Example mentioned below, however you can watch the solution on this link: https://www.youtube.com/watch?v=r4A46oBIwZk&t=8s
Warning: Line 13:8: React Hook React.useEffect has missing dependencies: 'history' and 'currentUser?.role'. Either include them or remove the dependency array react-hooks/exhaustive-deps
React.useEffect(() => {
if (currentUser?.role !== "Student") {
return history.push("/")
}
}, [])
Resolution: Step 1: Move business logic it to separate const.
Now the warning is: React Hook React.useEffect has a missing dependency: 'roleChecking'.
const roleChecking = () =>{
if (currentUser?.role !== "Student") {
return history.push("/")
}
}
React.useEffect(() => {
roleChecking()
}, [])
Last step is to create a reference to the function:
const roleRef = React.useRef();
const roleChecking = () => {
if (currentUser?.role !== "Student") {
return history.push("/");
}
};
roleRef.current = roleChecking;
React.useEffect(() => {
return roleRef.current();
}, [currentUser?.role]);
Solution 16 - Reactjs
It seems the fetchBusinesses
function declared in the component.
It means in every render it declares new function which triggers the hook.
There are 2 approaches to fix the issue.
-
Move the
fetchBusinesses
function declaration out of component. -
Wrap the
fetchBusinesses
function withuseCallback
hook.
First option is preferable.
Solution 17 - Reactjs
This is not an answer specific to the question use case but more general case, and cover the case when useEffect or extract and import is not warking. The useRef senario:
Sometimes the scenario is that useEffect should have the empty array and you still want to use inside the useEffect parts of the state but still you don't want inject them as dependencies, also you might tried the useCallback and now react is complains about the dependencies of the useCallback and you stuck. In this case in some cases you can use useRef. for example:
const locationRef = useRef(location);
useEffect(()=>{
const qs = locationRef.current.search
...
},[])
You should be careful when using this technique and be aware of that useRef is not activate a render process.
Solution 18 - Reactjs
Just pass the function as the argument in the array of useEffect...
useEffect(() => {
functionName()
}, [functionName])
Solution 19 - Reactjs
using UseEffect fetchBusinesses
calling function declare in useEffect()
by declaring a const variable after that calling the name of the function,
useEffect(()=>{
const fetchBusinesses=()=>{
console.log(useeffect fetchbussinesses functions)
}
fetchBusinesses();
},[declare the variable used in useeffect hooks ])
Solution 20 - Reactjs
You are making use of useEffect
and when you do that, very frequently you want to make use of some variables that are used as props or state inside your component.
There is a rule built into eslint that wants you to reference any different prop or piece of state inside of the useEffect
dependency array. Thats the array that controls when useEffect gets executed. That rule wants to see it listed inside that array which decides when to re-run the useEffect
function.
So you would need to add in [fetchBusinesses]
and the warning should be gone.
Now, why does that rule wants us to put that in there?
There are some scenarios where making use of useEffect
and not properly listing out all the pieces of state and props inside the array can lead to weird and hard to debug problems.
So this rule is to help avoid those hard to understand problems that can come up with useEffect
.
Now, arbitrarily adding to that array can lead to bugs as well. So either way you are running into bugs you have to solve. According to your comments that seemed to solve it for you, but I would have wanted to investigate further to see if you by any chance got a second GET
request in your Network tab in Chrome after adding in the fetchBusinesses
function to your useEffect
array.