React: How does ESLint know when a useEffect dependency can be omitted? - reactjs

The useEffect hook of React normally requires every external value used to be added to the dependencies array. There seem to be a few exceptions, like the setState function given from the second array item of useState, or dispatch from useReducer.
From what I've read, this is because React guarantees that setState will never change, so ESLint doesn't feel the need to require it in the dependencies. But how does ESLint know this? Is there a way for me to specify something as never-changing and let ESLint pick this up and allow me to omit the "fake dependency"?
An example:
function useCustomHook() {
const [state, setState] = useState(false);
return [state, setState];
}
function App() {
const [state, setState] = useState(false);
const [customState, setCustomState] = useCustomHook();
useEffect(() => {
setState(true);
}, []); // ESLint does NOT complain that setState is missing
useEffect(() => {
setCustomState(true);
}, []); // ESLint DOES complain that setCustomState is omitted
//...
}
setState and setCustomState above are virtually the same, so what triggers ESLint?

The React team maintains the React hooks ESLint plugin. This plugin is where the "smarts" you described are contained.
There's no simple way to add your own exceptions. I know there are plenty of cases where you know it's not needed, but it's best to just include your own functions as dependencies. If they are truly referentially stable, the diffing overhead is almost nothing. If they do change for any reason, you'll be glad it's listed as a dependency.

Related

React Hooks: useEffect with dependecy also called when component mounts

In React, you can use useEffect() hook with a dependency on it.
const [data, setData] = useState();
useEffect(() => {
console.log(data.test.test);
}, [data]);
So I would assume that this useEffect is just called when the data is changed.
So let´s assume I would make an api call and use setData(response.data).
Then the useEffect() should be triggerd, since the data state is a dependecy.
Then the console.log(data.tes.test) would be working, since I got the right data.
However, I get an undefined error, which comes from the fact that useEffect() is also called initially.
How can I avoid this? This is pretty annoying and in my mind not a good design with regard to adding a dependency.
You have two solutions. As #Woohaik has already mentioned, checking for data existence in the useEffect. I'd implement a guard:
useEffect(() => {
if (!data) return
console.log(data.test.test)
}, [data])
or you could make yourself acquainted with optional chaining (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining):
useEffect(() => {
console.log(data?.test?.test)
}, [data])

What is the correct solution of ReactJS Useeffect missing dependency issues on component did mount?

everyone
while I am working on React Function Component, I got the same problem everyday. it is useeffect dependency problem.
my working flow like the following to get the data by calling the RestAPI
define useeffect with empty array [] for component did mount event. I want run the calling api one time
add API calling function inside useeffect
my working code like the following.
const DevicesMain = () => {
console.log("Devices Main render")
const [state, dispatch] = useReducer(reducer, initialState)
const [sites, rooms, devices] = useSelector((state) => [state.sites, state.rooms, state.devices])
const reduxDispatch = useDispatch()
useEffect(() => {
console.log("did mount")
if (!sites.get) {
reduxDispatch(siteActions.getALL())
}
if (!rooms.get) {
reduxDispatch(roomActions.getALL())
}
reduxDispatch(deviceActions.getALLWithIncludeCount())
return () => {
console.log("will unmount")
if (subscription) {
subscription.unsubscribe()
}
}
}, [])
return (
<>
...
</>
)
}
after that, always I got the dependency missing Waring like the following.
React Hook useEffect has missing dependencies: 'reduxDispatch', 'rooms.get', 'sites.get', and 'subscription'. Either include them or remove the dependency array react-hooks/exhaustive-deps
exactly the above warning makes my headache. for some days, I tried to remove this warning.
at first, I added the dependencides that the warning message mentioned to me like the following.
}, [reduxDispatch, rooms.get, sites.get, subscription])
after adding like the above, useeffect function is running over 1 time, as that function has rooms.get and sites.get as its dependency.
as you see my the above image, there are serveral did mount log.
secondly, I disabled eslint like the following
// eslint-disable-next-line
}, [])
exactly, I think that this is very basic problem. but still I didn't find what is the best solution for useeffect one time run.
if you give me the best solution, I am thanksful
Regards
Your'e doing perfectly fine. It's a suggestion related to es-lint and not particularly react it self. disabling eslint in this case is perfectly fine and the right approach infact.
Explanation: Eslint basically makes you aware to add dependencies as arguments in array which you are using and which might change in future. As a critical human, your job is to note whether you'd want that behaviour. In this scenario you definitely don't want the behaviour so disabling eslint is the right and recommended way.
Note: Apart from this I would suggest to not listen for subscription on unmount because first of all you haven't defined a subscription variable and secondly there are no subscriptions(listnings) that your'e using in useEffect hook so no need for it.

Why React expects me to add setters as useEffects dependencies

I am trying to use an async function inside a useEffect callback.
There is a behavior i don't understand (in my case related to the throttle function from lodash).
I don't get what is happing, and how to solve it, here is a sample of the code:
import throttle from 'lodash/throttle';
const myRequestWrapped = throttle(myRequest, 300);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name) // No warning
myRequestWrapped(name); // React Hook useEffect has a missing dependency: 'myRequestWrapped'. Either include it or remove the dependency array
}, [ name ]);
If i add myRequestWrapped as a dependency, i have an infinite loop (the effect is triggered continuously).
I guess the throttle method works with a timer and it returns a different result at every run so i can understand why the infinite loop.
But i really don't understand why React wants it as a dependency (especially that it works without adding it !).
What is the logic?
Why myRequestWrapped and not myRequest?
Should i ignore the warning or do you know a clean way to solve that?
Thanks.
Its not React that wants you to add myRequestWrapped as a dependency but its eslint.
Also you must note that ESLint isn't aware of the programmers intention so it just warns the user if there is a scope of error being made.
Hooks heavily rely on closures and sometimes its difficult to figure out bugs related to closures and that is why eslint prompts if there is a case of a fucntion of variabled used within useEffect that might reflect the updated values.
Of course the check isn't perfect and you could carefully decide whether you need to add a dependency to useEffect or not.
If you see that what you wrote is perfectly correct. You can disable the warning
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ name ]);
Also you must not that throttle function cannot be used within render of functional componentDirectly as it won't be effective if it sets state as the reference of it will change
The solution here is to use useCallback hook. Post that even if you add myRequestWrapped as a dependency to useEffect you won't be seeing an infinite loop since the function will only be created once as useCallback will memoize and return the same reference of the function on each render.
But again you must be careful about adding dependency to useCallback
import throttle from 'lodash/throttle';
const Comp = () => {
const myRequestWrapped = useCallback(throttle(myRequest, 300), []);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
}, [ name ]);
...
}
Shubham is right: it's not REACT, but ESLint instead (or TSLint, depending on the Linter you are using).
If I may add, the reason why the Linter suggest you to add myRequestWrapped is because of how the closures work in JavaScript.
To let you understand, take this easier example (and I need this because I would need to know what it's inside myRequestWrapped:
const MyComp = props => {
const [count, setCount] = React.useState(0);
const handleEvent = (e) => {
console.log(count);
}
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, []);
return <button onClick={e => setCount(s => s +1)}>Click me</button>
}
So, right now, when MyComp is mounted, the event listener is added to the document.body, and anytime the click event is triggered, you call handleEvent, which will log count.
But, since the event listener is added when the component is mounted, the variable count inside handleEvent is, and always will be, equal to 0: that is because the instance of handleEvent created when you added the event listener is just one, the one that you associated with the event listener.
Instead, if you would write the useEffect like this:
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, [handleEevent]);
Anytime the handleEvent method is updated, also your event listener is updated with the one handleEvent, thus when clicking on the document.body you will always log the latest count value.

restrict useEffect execution to only one dependency

I´m playing with useEffect and i only need to "listen" to changes in one state but use another one without including it.
const [users, setUsers] = useState(["Mike"]);
const [message, setMessage] = useState();
useEffect(() => {
users.length > 0 && setMessage("we have users"!)
}, [users])
setMessage("hello!") // If I add this dependency to the previous useEffect it will trigger. but I will only need it to trigger on users change
setUsers(["john", "mary"])
doing this React will complain about the setMessage dependency
what´s the correct use of useState if I only one to listen to one state?
I ended up using. not sure if is the best approach. I´m just sharing what I tried :)
useEffect(() => {
users.length > 0 && functionIWantToExecuteOnceWithoutDependencies()
}, [users])
The eslint is giving warning specifying that you have missed adding setMessage as a dependency. But in your case, it seems the setMessage is a state setter and won't be changed thought your render cycle. Hence you can either ignore the eslint warning or disable the warning.
You can disable the warning like this:
useEffect(
() => {
users.length > 0 && setMessage("we have users"!)
// eslint-disable-next-line react-hooks/exhaustive-deps
[users],
);
Just add setMessage as a dependency. The react hook linter sees it as a called function, but since setMessage is a function created from the useState hook it is a stable reference so I won't cause any effect triggering.
You can disable lint rules per line if you want to, but that is generally not recommended. A scenario can arise where you update the effect, but because the dependency array linting is disabled you may introduce logical bugs.

useEffect dependency array and ESLint exhaustive-deps rule

I have a component that looks like this:
const MyComponent = props => {
const { checked, onChange, id } = props;
const [isChecked, setChecked] = useState(false);
useEffect(() => {
onChange && onChange({ isChecked: !!checked, id });
setChecked(checked);
}, [checked]);
const childProps = {
id,
isChecked
};
return <ChildComponent {...childProps} />;
};
The exhaustive-deps lint rule isn't happy:
React Hook useEffect has missing dependencies: id and onChange.
Either include them or remove the dependency array.
(react-hooks/exhaustive-deps)eslint
I know that id and onChange are not going to change, so adding them to the dependency array seems unnecessary. But the rule isn't a warning, it's a clear instruction to do something.
Is the ESLint rule:
1) Over-cautious and a bit dumb in this instance, so safe to ignore?
2) Highlighting best practice - i.e. to minimise unexpected bugs that might occur in future if, for instance, changes in parent components mean that id will change at some point in future?
3) Showing an actual/possible problem with the code as it currently is?
Old Answer
Actually the rule is very straightforward: Either pass an array containing all dependencies, or don't pass anything. So I guess the rule isn't dumb, it just doesn't know if the dependencies are going to change or not. So yes, if you are passing an array of dependencies it should contain ALL dependencies, including those you know for a fact that will not change. Something like this will throw a warning:
useEffect(() => dispatch({ someAction }), [])
And to fix this you should pass dispatch as a dependency, even though it will never change:
useEffect(() => dispatch({ someAction }), [dispatch])
Don't disable the exhaustive deps rule, as mentioned here
UPDATE 05/04/2021
As addressed here. This is no longer necessary since eslint pull #1950.
Now referential types with stable signature such as those provenients from useState or useDispatch can safely be used inside an effect without triggering exhaustive-deps even when coming from props
The way to look at it is every render has its own effect. If the effect will be the same with a particular set of values, then we can tell React about those values in the dependencies array. Ideally, a component with the same state and props, will always have the same output (rendered component + effect) after its render and effect is done. This is what makes it more resilient to bugs.
The point of the rule is that, if the deps DO change, the effect SHOULD run again, because it is now a different effect.
These 3 links also give more insights about this:
https://github.com/facebook/react/issues/14920#issuecomment-471070149
https://overreacted.io/a-complete-guide-to-useeffect/
https://overreacted.io/writing-resilient-components/

Resources