Reputation: 5941
Why is React forcing me with their linter plugin to add dependencies that I don't want?
For example, I want my effect to trigger only when a certan value changes, yet the linter tells me to add even functions to the dependencies, and I don't want that.
Why it forces me to do that? What do I gain from that?
/**
* Gets all items, pages, until 250th.
*/
useEffect(() => {
let mounted = true;
if (loadUntil250th && !paginationProps.complete) {
mounted && setLoading(true);
let limit = 250 - paginationProps.page * BATCH_LIMIT;
fetchListItems(paginationProps, limit, paginationProps.page * BATCH_LIMIT)
.then((results) => {
if (mounted) {
setPaginationProps({
...paginationProps,
page: 250 / BATCH_LIMIT,
autoLoad: false,
complete: paginationProps.totalItems <= 250,
});
setListItems(results.listItems);
setLoading(false);
}
})
.catch((err) => {
logger.log('LOADMORE FAILED:', err);
mounted && setPaginationProps({ ...paginationProps, complete: true });
mounted && setLoading(false);
});
}
return () => {
mounted = false;
};
}, [loadUntil250th]);
It wants this array of dependencies, which result in a infinite loop
[loadUntil250th, logger, paginationProps, setListItems]);
I want to understand why it is required, if I don't want them.
Upvotes: 3
Views: 773
Reputation: 56
The 'exhaustive-deps' lint rule is designed to protect against stale closures, where useEffect
references props or state used in the callback but not present in the dependency array. Since logger
, paginationProps
, and setListItems
can theoretically change between renders, it's not safe to reference them inside useEffect
without also including them in the dependency array to ensure you're always receiving and acting on up-to-date data. You can think of useEffect as essentially generating a snapshot of all state and props when it gets created and only updating that if one of its dependencies changes.
For instance, without including paginationProps
in the dependencies list, if fetchListItems
ever modifies the value of paginationProps
then useEffect
won't have access to that updated value until loadUntil250th
changes.
As referenced in this answer, part of the issue is that your usage of useEffect() is unidiomatic. If all you're doing is subscribing to changes to loadUntil250th
, you'd be better off moving this function elsewhere and calling it with your code that modifies loadUntil250th
.
If you want to keep your code in the useEffect
hook, you have a few options:
paginationProps
and setPaginationProps
are derived from a useState
hook, you can eliminate the dependency on paginationProps
by passing a function to setPaginationProps
instead of an object. So your code would become:setPaginationProps(paginationProps => {
...paginationProps,
page: 250 / BATCH_LIMIT,
autoLoad: false,
complete: paginationProps.totalItems <= 250,
});
setListItems
inside the useEffect
hook if possible. This ensures that you can control whatever props/state that function depends on. If that's not possible, you have a few options. You can move the function outside the component altogether to guarantee that it doesn't depend on props or state. Alternatively, you can use the useCallback
hook along with the function to control its dependencies and then list setListItems
as another dependency.logger
function changes between renders, so you're probably safe keeping that inside your dependency array (although it's odd that the linter would expect that).If you're still curious, this article is helpful at detailing how useEffect
and the dependency array actually works.
Upvotes: 2