-
-
Notifications
You must be signed in to change notification settings - Fork 26.7k
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
How to disable the rule react-hooks/exhaustive-deps? #6880
Comments
You can put Some references for how to write code with correctly declared dependencies:
If you post a complete example I can try to look at how to fix your case. |
Thanks for the quick response @gaearon. I've been through those links you mentioned. I've got about 60+ of these warnings throughout my app. Most of the cases involve a dependency that doesn't update the effect, e.g. when calling redux actions, I have a lot of CSS animation logic related to when a slide enters or leaves a slideshow, where I only need to know if one or two dependencies changed (isEntering, isLeaving). Is there any good reason why I need to list deps that I know aren't needed? If I list the rest of the deps then those might mess up the animation logic unless I add more checks in the effect which makes it a lot more complex than it needs to be. (at least in comparison with class components) I could list a few examples if you would like, but like I explained above it's mostly prop functions causing these issues for me. |
Yes. Functions capture state and props inside of them. It just so happens that React Redux currently uses classes (or does it even anymore?) with methods that have stable identity. (And in that case adding them to the array doesn't hurt either.) But in general, you shouldn't expect that calling an arbitrary function from an effect is safe without declaring that function as a dependencies. Here's an example where "skipping" a function dependency produces a bug: facebook/react#14972 (comment). (This particular example is about
I'd be happy to look at a concrete case if you can provide one. Maybe the lint rule itself has some false positives that we can fix? |
For us, one of the primary places we're receiving this warning is for As a concrete example, the app we're working on can display a small notification in the top right corner that automatically disappears after 15 seconds. Here's a super trimmed down version of what that might look like:
This complains because Is there a better way to design this? Or would this be a false positive? |
I suppose we could wrap |
This is one of the reasons I don't want to add "unnecessary" things to my deps, as it makes the code look more complex and confusing. I totally agree that empty brackets tell you that it's CDM. There are many cases in my app where I just want to fetch once when the component mounts, but now I need to choose whether I want to add bunch of |
I understand that we need to re-think how we write our components using hooks, instead of trying to figure out how class lifecycle methods (CDM, CDU, etc.) correspond to hooks. But I must admit that this rule has made me feel less happy about hooks, mostly due to the complexity around simple things such as CDM. Like discussed above, I just want to use empty brackets in the deps knowing that the logic inside the effect will run once during mount. But now with the eslint rule I can't do that unless I start adding the I've looked at many libraries using hooks and most of them violate this rule. When I use the This just doesn't feel right. I'm now forced to add My suggestion would be to add new hooks or an option that would disable this rule, before more people start running into this same problem. We are used to the class lifecycle methods, perhaps we just need more official hooks from the React team that correspond to them. E.g. |
@WesCossick Is there a reason you can't move the |
But then he would need to add the Redux action to the dependency list, since the |
Good point @raRaRa. So in this post and example @gaearon uses a https://overreacted.io/making-setinterval-declarative-with-react-hooks/ So could @WesCossick do the same? |
Yeah he could do the same by storing the Redux action in a For me the main problem here is the complexity around achieving simple lifecycle methods such as Imagine going through this code as a beginner:
I wouldn't want to be in his position. |
@SCasarotto, in addition to what @raRaRa stated, it wouldn't be possible to move In regards to using Further, this and the previously mentioned I'd argue that the way it's designed presently, static code analysis for the |
I converted a large project to hooks and ran into the same issues as everyone else (200 class components). While initially I was skeptical about this rule, I ended up fixing all the warnings and it felt pretty good at the end. I think Dan's point that if you just have a useReducer with an empty dependency array, you are probably writting buggy code. In reality if you use redux and passing in some dispatch action for example, it felt unnecessary to list it as a dependency. While I agree with this sentiment, it does make you get into the habit of doing the right thing. There were some issues, like I had a helper function that took in props, and if I just use the auto-fix feature it would pass in props as a dependency and cause an infinite mounting bug. So I ended up disabling auto-fixing for this rule in lint-staged. Also, refactoring these types of code felt pretty good but they can be very difficult to do if you have huge cDM and cDU functions. |
So far in our experience the issues caused by missing dependencies have been significantly worse than losing the quick “[] means mount” visual shortcut. One thing I’ve noticed is that it’s often Redux users who complain about this. I think it’s likely because React Redux encourages the “action creator” pattern where you get a bunch of methods and then call them individually. Of course, declaring every method and thinking about them can get confusing if you have dozens. You wouldn’t have this problem if you weren’t using the “action creator” pattern. For example, with Finally, please remember nothing prevents you from creating your own |
Regarding the But in this specific example the idiomatic solution is to put I’d be happy to look into this in more detail if there was a sample project with a complete example. |
By the way. I know some people have very strong negative reaction to suppressing lint rules. Maybe it’s cultural and depends on a project. We also had ~60 suppressions when we added this lint rule to FB codebase. Arguably we’re the biggest user of Hooks yet as we started earliest and use them very actively. Out of those 60 suppressions, about half were either actual bugs or bugs waiting to happen. For most of the rest, there was usually a way to restructure the code in a more idiomatic way as to not trigger the warning and make the code itself more resilient. I think by now we have only about ~30 suppressions left and people rarely introduce new ones. We think the tradeoff is worth it and we don’t feel bad about adding suppressions to:
I know some teams might have zero tolerance for suppressions which IMO is counterproductive but I could see how in such a climate this strategy is harder to make work. However again, you always have an escape hatch of making your own Hook with explicitly different semantics. |
Could there be some semantic thing to the rule where specifying a literal like sandbox: example: import React from "react";
import ReactDOM from "react-dom";
function App() {
const [, setCount] = React.useState(0);
React.useEffect(() => {
setInterval(() => setCount(c => c + 1), 3000);
}, [setCount]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<UseEffectDidMount />
</div>
);
}
function UseEffectDidMount() {
React.useEffect(() => {
window.alert("I only happen once (didMount)");
}, ["onlyOnMount"]);
return null;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement); |
After going through one of our apps and restructuring everything to avoid errors, I do agree that it's appropriate to work around the warnings thrown for using Redux functionality in a Hook. For us, that meant destructuring the dispatch function ( Even still, after the restructuring we did have to suppress the I did notice, though, that almost exclusively we were suppressing the rule for Sure, we could roll our own solution and create a reusable FWIW, we also make heavy use of a modified version of |
I'm happy to consider individual cases, like I did in facebook/react#14920. (I asked for feedback at that point — it's unfortunate that you didn't see that thread and didn't offer your examples earlier.) Please let's keep the discussion grounded in concrete CodeSandbox cases because we've already learned discussing this in abstract doesn't work. I'd also prefer that these examples show real use cases and not abstract ones like #6880 (comment) does. You can use facebook/react#14920 (comment) as examples of how to explain your use case in a way that is descriptive enough to offer concrete advise. As I mentioned earlier, the case of a component "hiding itself" is dubious, and it would make more sense to me if a parent "notifications list" component managed and cleaned up "old" notifications in state instead. I'm happy to look at more concrete cases which need to be in CodeSandbox form. |
Hey @gaearon, we weren't using React at our company back on Feb 21 when you posted facebook/react#14920, so we couldn't have offered any feedback at that time. I'd be happy to offer concrete CodeSandbox cases based on real use cases. But before I do so, I'd like to make sure we're on the same page. This discussion has come a long way from its original question. In my most recent comment, I pointed out a common need in our apps is to have Suppressing the ESLint rule is not necessarily bad, but seems undesirable. Creating a custom Hook sidesteps the ESLint rule entirely, which is definitely not desirable. I believe we agree on these two things, but correct me if I'm wrong. With all this in mind, and assuming many apps would need a way for However, your latest comment seems to imply that it's almost always possible to refactor a use case so that it doesn't require mount only or update only behavior. And therefore it wouldn't make sense to even consider built-in support for those behaviors. If that's a correct assessment of your comment, I'd be happy to provide CodeSandbox cases that demonstrate where we felt we needed those behaviors. At that point, I assume we'll decide whether those are indeed reasonable use cases or if the designs need to be refactored. If that's not what your comment implies, and you don't want to consider built-in support for those behaviors, please explain what you would like the CodeSandbox cases to show. |
In my experience many cases people in which people want to get around the rule have more idiomatic fixes that don’t break it. But people don’t often see these solutions immediately because it takes a bit of a mental shift. Another thing I’ve noticed is that many proposed scenarios where somebody thinks they want “mount only” behavior have legit reasons to run on updates too. Such as when people forget props change over time, or don’t take parent state into account. My goal is to collect as many of those concrete scenarios as possible and to think through them all so that we have a set of recommendations I can later point people to. My intuition is that most of these recommendations will have idiomatic solutions instead of suppressing the rule. But maybe I’m wrong and I’d like to collect more examples where that doesn’t make sense too. If there’s a sufficient number or those maybe we can make the rule less aggressive, for example, or offer an easier way to partially suppress it. However, I haven’t seen enough cases that are justifiable yet. Hope that helps! |
The notion that some things need to happen only the first time user sees something is true. However associating it with “mount” is often a mistake. Such as conceptually a page navigation is often considered a fresh “appearance” even if technically the same |
I went to go make a codesandbox to demonstrate mount-only behavior, but after writing it up, I realized that there was a different way I could write it that would make it more resilient to re-renders. I feel like the big idea that people are missing is that they can have an effect that runs on mount, and properly cleans up after itself, and re-runs when deps change. I see what you mean, and some of us are (slowly) getting it. While we want to map lifecycles to a hook equivalent mentally to better understand them, it isn't a perfect translation and we really do need to change our mental model around this to making our effects resilient to re-renders which helps avoid the bugs that you have been showcasing when props change over time, but the component doesn't remount. I feel like most cases where you think you only want to run an effect once, you could run it again as deps change if you pass an appropriate cleanup function to the effect. |
I think this all comes down to code readability & predictability. But I totally agree with @gaearon that it takes a bit of a mental shift to get this right. I had an issue with
But is this safe? using the functional update form of And here's the code before the fix, which required the animation state to be dependency and caused recursion:
I'm basically generating a style object from the animation state. Another potential solution is to use
Would it be better/safer to use the To give context on how the styleObj is used:
|
That makes sense, @gaearon. I'm relatively new to React, so I'm definitely open to rethinking how we approach design choices for our apps. It seems most of the times we need the Recovering form stateAs a user navigates around a single page app, the goal is to have form state recovered to what they left it as. This only applies to using the browser's forward and back buttons to navigate to points in history they've already visited. Because it's a real world example, it's powered in-part by React Router and Formik. Here's the CodeSandbox editor and here's just the rendered app. For browser history state to work properly, you cannot use the CodeSandbox editor's forward, back, or refresh buttons; you must use just the rendered app in your browser. Recovery in action:
The That file also uses a custom If the Possible alternative designs for the
|
@raRaRa Do you mind posting a full example for animation case that I could run? Usually when you start reaching for the functional setState, the code can be further simplified with useReducer. |
Thanks for a concrete example. I’m on my phone so can’t test it in depth. But I’m curious — is there any particular reason you want to fill in the initial state from an effect, as opposed to actually using the initial state for this? (In case of Formik, I imagine it has some prop to set initial values.) I think that would remove your need for a “first render only” effect to fill the state. |
Curiously your desire to break the rule here does uncover the problem in an app. I think that if you open it in two tabs, diverging edits from those would overwrite one another. So one of them wouldn’t get restored. While not likely, it can be a problem. To solve it you could store some kind of revision ID in the storage too. In that case you would want to run the effect on update too. Instead of always replacing the state, it would offer to either resolve conflict (if unexpected revision is in the store), or ignore the update (if revision belongs to this tab). That could let you keep tabs in sync. https://overreacted.io/writing-resilient-components/#principle-3-no-component-is-a-singleton is a relevant principle. Of course it might be a higher bar than most apps care about. But I’m just pointing out that even here, the lint rule points out a deeper flaw in the design and forces you to confront it. (Or hack around it.) |
Hey @gaearon, the discussion has carried on quite a bit, but I wanted to see if you had any further thoughts on the state recovery example I provided? Is it possible to refactor that example? |
Regardless it would be good if we could turn off this rule in eslint for us people who want to live on the edge. It has good intentions but I don't want to unnecessarily memoize all the functions I use in an effect, i.e. const authed = useStore(state => state.user.authed);
const setAuthModalOpen = useActions(actions => actions.app.setAuthModalOpen);
useEffect(() => {
setAuthModalOpen(!authed);
}, [authed]); I regularly call methods created in the render function, would you guys recommend memoizing them all? I think this would increase code complexity in this case? Or else it would be handy to choose to consciously ignore them (but not everything): useEffect(() => {
setAuthModalOpen(!authed);
}, [authed]); // ignore: setAuthModalOpen, fn2, ... So far I've found I'm looking for variable changes, not functions. |
The lint rule is able to catch most subtle bugs so overall I think it's a net win. |
I'm using firebase and the db dependancy is failing eslint, but I can't use that as dependancy because it updates every .2s (it's a realtime database). Any ideas?
|
I use firebase a lot. I just never store a reference to |
Move declaration of db inside useEffect may help. You haven't shown how you defined db. |
@SCasarotto can you show an example of how you would query and update it with hooks? Moving the code above outside the component also works but I need some props to be sent to the method |
From one of my production apps I recently built with hooks:
As for "query and update" see something like:
|
Interesting @SCasarotto, why do you return the disposer to run the watcher? |
(the goal here is to only scroll back to the saved location of this page if we are coming from a child page). Both of these variables are in redux, but the effect should only be run when one of them has changed, not both of them. (effectively scrollPosition is constantly recorded, but meaningless, unless previousLocation has just changed). Is this an appropriate use of the eslint-disable? Is this the correct usage of an effect? |
I've read through this thread twice now and I still don't quite grasp a clean and terse way to use |
For this issue, we aren't planning to [optionally] disable react-hooks/exhaustive-deps because its advantages outweighs any inconveniences. If you are migrating old code, you are better off fixing these warnings rather than trying to disable them. If you need help, please create a codesandbox rather than a small code snippet and we may be able to help. Thanks. |
@bugzpodder Could you please comment on the use case I posted above, thank you. |
@gryn please provide a minimal repro using codesandbox.io rather than a code snippet. |
|
@samuelmataraso in that case you need to add that function as dependancy or declare it either outside of the component or inside the use effect and then call it |
@samuelmataraso as @bovas85 said, you can do this:
|
@bugzpodder I'm not sure how complete you would want this example. I have left a large comment explaining the purpose of the effect: https://codesandbox.io/s/infallible-hellman-mdech |
Hi @gryn, the codesandbox should be a working app demonstrating real app behavior. Without it, we can't really play around with it and can only guess in terms of what the actual fix to be. If the codesandbox is just a stub, then it defeats the purpose of making it. |
@bugzpodder I have updated the example to be "fully functional" (I still have not opted to include redux, but have stored the state in the top level component as a substitute). I have added documentation to the entire file, including what you should do to exercise the critical path. |
MobX is a pretty good example of where injecting dependencies isn't necessary, but we get the annoying linting warnings with no way to turn them off.
|
@gryn thanks for producing a working example. here is my fork of your sandbox: https://codesandbox.io/s/elegant-austin-qmqob
@bluepeter I don't have experience into mobx or why their documentation recommends an empty dependency array. In my experience with redux, the dispatch function never changes but I put it in the dependency array anyway. Does it work if you put the store in the dependency array (fully realizing that the docs did not recommend this approach) |
@bugzpodder passing the stores into the dependency does seem to work. It seems somewhat inelegant, though. @FredyC can probably articulate better than I can why it's not recommended to pass in MobX store dependencies into |
MobX "store" has stable referential integrity, it will never change unless doing that on purpose. It does work to specify it in dependencies, but as @bluepeter says, it's highly inelegant as it just clutters the code without any benefit. I still haven't got into using ESLint with CRA, TSLint is good enough for now and because of these false positives I am probably going to avoid it for a long time. Similarly, @jaredpalmer had issues where it was forcing him to |
@bugzpodder Jack, thank you for your feedback. I agree that the code is not excellent the way it is! I am working within a large application and sometimes we don't have the luxury of fixing what we find. However, I can fix the issue with the way the scroll saving works (we will still need to store it in redux, since the component recording the position will unmount when the page changes, but I can pass a ref down to the wrapped component, instead of a prop). My take away from this is that you can use references for "consumable" information, and props for "actable" information. That is, "actable" means it either directly effects rendering, or can directly cause action. "Consumable", on the otherhand, is useful only during an action, and is not the cause of one. Perhaps there are better words for it, and perhaps I am still missing some subtlety. In anycase, you've helped me move forward and hopefully improved my understanding. Thank you. |
Since my comment about action creators got "buried" in the collapsed thread, I'll post it again here: #6880 (comment). TLDR: Hooks don't mesh very well with the "action creator" pattern. The recommendation is to |
I'm trying to disable the rule
react-hooks/exhaustive-deps
. I've tried adding"react-hooks/exhaustive-deps": "off",
to .eslintrc without any luck.How do I disable this rule? Thanks!
The text was updated successfully, but these errors were encountered: