useEffect dependency array and ESLint exhaustive-deps rule - reactjs

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/

Related

are missing dependencies in useEffect *always* mean bad design?

I have this piece of code in my react component:
const [editOrderComponentKey, setEditOrderComponentKey] = useState(0)
// clear/reload EditOrderPanel on a freshly loaded order
useEffect(() => {
setEditOrderComponentKey(editOrderComponentKey + 1)
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [singleOrder])
the purpose of the code above, is to change the editOrderComponentKey when singleOrder has changed. I cannot put the editOrderComponentKey in the dependencies list, as the linter asks, because it will cause an infinite rendering, easy to see why.
anyway, this effect comes handy because few lines below I have the following code:
<EditOrderPanel orderId={orderId} key={`editOrder${editOrderComponentKey}`}/>
I want to invalidate EditOrderPanel if and only if singleOrder changes, so that EditOrderPanel would be re-mounted, and EditOrderPanel's local state would get its defaults anew (useState default).
is there a way to design the code such that no linter disablement would be required on that line, and still being able to use uncontrolled components?
In this case you can use the callback version to get the previous value.
const [editOrderComponentKey, setEditOrderComponentKey] = useState(0)
useEffect(() => {
setEditOrderComponentKey(prev => prev + 1)
}, [singleOrder, setEditOrderComponentKey])

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.

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

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.

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.

setState in React's useEffect dependency array

What's the idea behind defining the setState values inside useEffect's dependency array?
const [someState, setSomeState] = useState();
...
useEffect(() => {
// Do something
setSomeState('some value');
}, [setSomeState]);
React Hook useEffect has a missing dependency: 'setSomeState'.
Either include it or remove the dependency array.
eslint(react-hooks/exhaustive-deps)
Not exactly sure if this example would cause eslint to ask to define setSomeState in the dependency array, but I have seen this kind of moments when writing with useEffect. What is React listening to in this case? My understanding is that useEffect listens for changes in the values in the dependency array, but would setSomeState ever change? What am I missing here?
In this case, no, the useState setter function will never change, but useEffect doesn't know it is a function from another hook, it just sees a function.
Consider this use case:
const MyComponent = ({ onUpdate }) => {
const [state, setState] = useState();
useEffect(() => {
// state updated, call onUpdate callback with state
onUpdate(state);
}, [onUpdate, state]);
return ...
}
Now parent component can, given
<MyComponent onUpdate={onUpdateHandler} />
define onUpdateHandler as
let onUpdateHandler = console.log;
then later change it while still mounted
onUpdateHandler = state => dispatch({ type: 'SAVE_STATE', state });
By placing the callback in the dependency array the effect hook see the value of onUpdate changed and trigger the effect again. If it didn't include the callback then it would continue to console.log state updates instead of now handling it differently.
It is more likely for values to change than it is for functions to change, but function values can change. You likely have an eslinter that is recommending you add all the variables used within the hook to the dependency array, and this will in most cases be the correct thing to do. A few exceptions are for example, when you want an effect to compute ONLY when the component mounts by specifying an empty array, or not including some variables that are functions that you KNOW will never change during the life of the component, and in these cases you can use the eslint-disable-next-line react-hooks/exhaustive-deps override to unflag it as a warning. Caution though, as this will disable the linting checks so it is possible for bugs to creep in if not paying attention.

Resources