-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add ESLint rule for useEffect/useCallback/useMemo Hook dependencies #14052
Conversation
Details of bundled changes.Comparing: 8a12009...8797fa6 eslint-plugin-react-hooks
Generated by 🚫 dangerJS |
What do you think about rule semantics? Would you change anything? |
I'd like to add a warning if you have |
Can you explain what breaks? |
Well – the first time you run the code it will be null, then the second time (and every time thereafter) it will be the DOM node. So you end up with this situation of the effect running in the render after the ref changes. Like
looks like it will set that property when the div mounts, but it doesn't. Similarly, if the ref actually moves to a new node then you still end up executing the effect in the first render after that happens. I'm not aware of any valid use case for writing this. (In contrast, you might want to lazy initialize a ref in the render phase and refer to it during commit, and I think that is OK.) |
Worth mentioning that I initially designed this lint rule to be pretty opinionated. That is, in the common 80% case this rule should be useful, but in 20% of exceptional cases this lint will over-fire. For instance the useEffect(
() => {
if (props.updateTitle) {
document.title = props.title;
}
},
[props.updateTitle, props.updateTitle ? props.title : undefined]
); For this effect the best possible dependency list includes the conditional. In its current form the lint rule will reject this example. Supporting this case is tricky since if you accept the above should you also reject this: useEffect(
() => {
if (props.updateTitle) {
document.title = props.title;
}
},
[props.updateTitle, props.updateTitle ? undefined : props.title]
); This example is wrong, but if you want the lint rule to reject it now you’re in the business of tracking conditionals. I like this rule as-is on the 80/20 principle, but given its limited semantics should it really be enabled by default in the official React Hooks eslint plugin? This rule could be community managed say in |
Yeah, I'm a bit worried about false positives here. |
At the same time, if we make it too loose it's only going to catch "obvious" things and will let through the more subtle bugs |
We could also add a level to the rule. So people can choose between more strict and more relaxed. |
It's sometimes hard to codify these things as "levels", but we can probably group together pieces of logic as options to toggle. I'll try to do that |
Anecdotally, I've noticed that underspecifying dependencies is alarmingly common. In many (most?) cases you're better off omitting the dependencies list entirely. Since mistakes are common, and you usually have the option remove the list, I don't mind so much if this particular rule isn't perfect. However, if false positives are the main blocker, I propose that the rule bails out on more complex cases. If the dependencies list is anything other than a list of variables (e.g. contains conditional expressions or function calls) it should assume the user knows what they're doing. |
Alternatively, it could forbid complex expressions. They're probably not a good idea anyway, unless they're automatically generated by a compiler. I would even consider going so far to forbid property access in the list and in the effect (except for methods I guess?) though I don't know if that's feasible. |
That would make it a problem to have an effect that only runs when, say, a particular prop changes. Property access is also required to access refs. |
Might be better to bail out only when there is no list currenty there. Otherwise it should probably do its best to add more dependencies. Otherwise it may bail out when it could be most useful |
@Kovensky
The idea is you should be picking off specific props anyway.
Correct, because they are mutable. I was thinking you could make an exception for refs and forbid all other mutable access. But maybe this is too pedantic and/or complicated to implement. |
Oh definitely I assumed that's what it already does |
bf7c57b
to
b5bac9d
Compare
I pushed some failing tests. (cc @jamiebuilds @calebmer) I will work on them tomorrow unless somebody beats me to a fix. |
ade085e
to
dc9bb2e
Compare
dc9bb2e
to
8797fa6
Compare
I'm going to take a different approach in #14636. |
Moving from #14048 and gaearon#3
#14048:
gaearon#3: