React: [ESLint] Feedback for 'exhaustive-deps' lint rule

Created on 21 Feb 2019  ·  111Comments  ·  Source: facebook/react

Common Answers

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡

We analyzed the comments on this post to provide some guidance: https://github.com/facebook/react/issues/14920#issuecomment-471070149.

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡


What is this

This is a new ESLint rule that verifies the list of dependencies for Hooks like useEffect and similar, protecting against the stale closure pitfalls. For most cases it has an autofix. We'll add more documentation over the next weeks.

autofix demo

Installation

yarn add eslint-plugin-react-hooks@next
# or
npm install eslint-plugin-react-hooks@next

ESLint config:

{
  "plugins": ["react-hooks"],
  // ...
  "rules": {
    "react-hooks/rules-of-hooks": 'error',
    "react-hooks/exhaustive-deps": 'warn' // <--- THIS IS THE NEW RULE
  }
}

Simple test case to verify the rule works:

function Foo(props) {
  useEffect(() => {
    console.log(props.name);
  }, []); // <-- should error and offer autofix to [props.name]
}

The lint rule complains but my code is fine!

If this new react-hooks/exhaustive-deps lint rule fires for you but you think your code is correct, please post in this issue.


BEFORE YOU POST A COMMENT

Please include these three things:

  1. A CodeSandbox demonstrating a minimal code example that still expresses your intent (not "foo bar" but actual UI pattern you're implementing).
  2. An explanation of the steps a user does and what you expect to see on the screen.
  3. An explanation of the intended API of your Hook/component.

please

But my case is simple, I don't want to include those things!

It might be simple to you — but it’s not at all simple to us. If your comment doesn't include either of them (e.g. no CodeSandbox link), we will hide your comment because it’s very hard to track the discussion otherwise. Thank you for respecting everyone’s time by including them.

The end goal of this thread is to find common scenarios and transform them into better docs and warnings. This can only happen when enough details are available. Drive-by comments with incomplete code snippets significantly drive down the quality of the discussion — to the point that it's not worth it.

ESLint Rules Discussion

Most helpful comment

We did a pass over these with @threepointone today. Here's a summary:

Fixed in the Lint Rule

Extraneous useEffect dependencies

The rule doesn't prevent you from adding "extraneous" deps to useEffect anymore since there are legit scenarios.

Functions in the same component but defined outside of the effect

Linter doesn't warn for cases where it's safe now, but in all other cases it gives you better suggestions (such as moving the function inside the effect, or wrapping it with useCallback).

Worth Fixing in User Code

Resetting state on props change

This doesn't produce lint violations anymore but the idiomatic way to reset state in response to props is different. This solution will have an extra inconsistent render so I'm not sure it's desirable.

"My non-function value is constant"

Hooks nudge you towards correctness wherever possible. If you do specify the deps (which in some cases you can omit), we strongly recommend to include even the ones that you think won't change. Yes, in this useDebounce example the delay is unlikely to change. But it's still a bug if it does, but the Hook can't handle it. This shows up in other scenarios, too. (E.g. Hooks are much more compatible with hot reloading because every value is treated as dynamic.)

If you absolutely insist that a certain value is static, you can enforce it.
The safest way is to do so explicitly in your API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Then it clearly can’t change unless you put it inside render. (Which would not be idiomatic usage of your Hook.) But saying that <Slider min={50} /> can never change isn't really valid — somebody could easily change it to <Slider min={state ? 50 : 100} />. In fact, someone could do this:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

If someone switches isCelsius in state, a component assuming min never changes will fail to update. It's not obvious in this case that the Slider will be the same one (but it will be because it has the same position in the tree). So this is a major footgun in terms of making changes to the code. A major point of React is that updates render just like the initial states (you can't usually tell which is which). Whether you render prop value B, or whether you go from prop value A to B — it should look and behave the same.

While this is unadvisable, in some cases, the enforcement mechanism could be a Hook that warns when the value changes (but provides the first one). At least then it's more likely to be noticed.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

There may also be a legitimate case where you just can't handle an update. Such as if the lower level API doesn't support it, like a jQuery plugin or a DOM API. In that case warning is still appropriate so that the consumer of your component understands it. Alternatively, you can make a wrapper component that resets the key on incompatible updates — forcing a clean remount with fresh props. That's probably preferable for leaf components like sliders or checkboxes.

"My function value is constant"

First of all, if it's constant and hoisted to top level scope then the linter won't complain. But that doesn't help with things coming from props or context.

If it truly is constant then specifying it in deps doesn't hurt. Such as the case where a setState function inside a custom Hook gets returned to your component, and then you call it from an effect. The lint rule isn't smart enough to understand indirection like this. But on the other hand, anyone can wrap that callback later before returning, and possibly reference another prop or state inside it. Then it won’t be constant! And if you fail to handle those changes, you’ll have nasty stale prop/state bugs. So specifying it is a better default.

However, it is a misconception that function values are necessarily constant. They are more often constant in classes due to method binding, although that creates its own range of bugs. But in general, any function that closes over a value in a function component can't be considered constant. The lint rule is now smarter about telling you what to do. (Such as moving it inside the effect — the simplest fix — or wrapping it with useCallback.)

There is a problem on the opposite spectrum of this, which is where you get infinite loops (a function value always changes). We catch that in the lint rule now when possible (in the same component) and suggest a fix. But it's tricky if you pass something several levels down.

You can still wrap it in useCallback to fix the issue. Remember that technically it is valid for a function to change, and you can’t ignore this case without risking bugs. Such as onChange={shouldHandle ? handleChange : null} or rendering foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> in the same spot. Or even fetchComments which closes over the parent component state. That can change. With classes, its behavior will change silently but the function reference will stay the same. So your child will miss that update — you don't really have an option outside of passing more data to the child. With function components and useCallback, the function identity itself changes — but only when necessary. So that's a useful property and not just a hindrance to avoid.

We should add a better solution for detecting infinite async loops. That should mitigate the most confusing aspect. We might add some detection for this in the future. You could also write something like this yourself:

useWarnAboutTooFrequentChanges([deps]);

This is not ideal and we’ll need to think about handling this gracefully more. I agree cases like this are pretty nasty. The fix without breaking the rule would be to make rules static, e.g. by changing the API to createTextInput(rules), and to wrap register and unregister into useCallback. Even better, remove register and unregister, and replace them with a separate context where you put dispatch alone. Then you can guarantee you’ll never have a different function identity from reading it.

I'd add that you probably want to put useMemo on the context value anyway because the provider does a lot of calculation that would be sad to repeat if no new components registered but its own parent updated. So this kind of puts the problem you might have not noticed otherwise more visible. Although I agree we need to make it even more prominent when this happens.

Ignoring function dependencies completely leads to worse bugs with function components and Hooks because they would keep seeing stale props and state if you do that. So try not to, when you can.

Reacting to Compound Value Changes

It's weird to me why this example uses an effect for something that's essentially an event handler. Doing the same "log" (I assume it could be a form submit) in an event handler seems more fitting. This is especially true if we think about what happens when component unmounts. What if it unmounts right after the effect was scheduled? Things like form submission shouldn't just "not happen" in that case. So it seems like effect might be a wrong choice there.

That said you can still do what you tried — by making the fullName be setSubmittedData({firstName, lastName}) instead, and then [submittedData] is your dependency, from which you can read firstName and lastName.

Integrating with Imperative/Legacy Code

When integrating with imperative things like jQuery plugins or raw DOM APIs, some nastiness may be expected. That said I'd still expect you to be able to consolidate the effects a bit more in that example.


Hope I didn't forget anyone! Let me know if I did or if something's unclear. We'll try to turn the lessons from this into some docs soon.

All 111 comments

This example has a reply: https://github.com/facebook/react/issues/14920#issuecomment-466145690

CodeSandbox

This is an uncontrolled Checkbox component which takes a defaultIndeterminate prop to set the indeterminate status on initial render (which can only be done in JS using a ref because there's no indeterminate element attribute). This prop is intended to behave like defaultValue, where its value is used only on initial render.

The rule complains that defaultIndeterminate is missing from the dependency array, but adding it would cause the component to incorrectly overwrite the uncontrolled state if a new value is passed. The dependency array can't be removed entirely because it would cause the indeterminate status to be fully controlled by the prop.

I don't see any way of distinguishing between this and the type of case that the rule is _meant_ to catch, but it would be great if the final rule documentation could include a suggested workaround. 🙂

Re: https://github.com/facebook/react/issues/14920#issuecomment-466144466

@billyjanitsch Would this work instead? https://codesandbox.io/s/jpx1pmy7ry

I added useState for indeterminate which gets initialized to defaultIndeterminate. The effect then accepts [indeterminate] as an argument. You currently don't change it — but if you did it later, I guess that would work too? So the code anticipates future possible use case a bit better.

So I got this following (edge?) case where I pass some html and use it with dangerouslySetInnerHtml to update my component (some editorial content).
I am not using the html prop but the ref where I can use ref.current.querySelectorAll to do some magic.
Now I need to add html to my dependencies in useEffect even though I am not explicitly using it. Is this a use case where I actually should disable the rule?

The idea is to intercept all the link clicks from the editorial content and track some analytics or do other relevant stuff.

This is a watered down example from a real component:
https://codesandbox.io/s/8njp0pm8v2

I'm using react-redux, so when passing down an action creator in the props from mapDispatchToProps, and using that action creator in a hook, I get a exhaustive-deps warning.

So I can obviously add the redux action to the dependency array, but because the redux action is a function and never changes, this is unneccessary right?

const onSubmit = React.useCallback(
  () => {
    props.onSubmit(emails);
  },
  [emails, props]
);

I expect the lint to fix the deps into [emails, props.onSubmit], but right now it always fix the deps into [emails, props].

  1. A CodeSandbox demonstrating a minimal code example that still expresses your intent (not "foo bar" but actual UI pattern you're implementing).

https://codesandbox.io/s/xpr69pllmz

  1. An explanation of the steps a user does and what you expect to see on the screen.

A user will add an email and invite those email to the app. I intentionally remove the rest of the UI to just the button because they're irrelevant to my issue.

  1. An explanation of the intended API of your Hook/component.

My component has a single prop, onSubmit: (emails: string[]) => void. It'll be called with the emails state when a user submit the form.


EDIT: Answered in https://github.com/facebook/react/issues/14920#issuecomment-467494468

This is because technically props.foo() passes props itself as this to foo call. So foo might implicitly depend on props. We'll need a better message for this case though. The best practice is always destructuring.

CodeSandbox

It doesn't consider that mount and update can be distinctly different when integrating when 3rd party libs. The update effect can't be included in the mount one (and removing the array altogether), because the instance shouldn't be destroyed on every render

Hi,

Not sure what's wrong with my code here :

const [client, setClient] = useState(0);

useEffect(() => {
    getClient().then(client => setClient(client));
  }, ['client']);

I got React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

@joelmoss @sylvainbaronnet I appreciate your feedback but I hid your comments because they didn't include the information we asked for at the top of the issue. That makes this discussion unnecessarily difficult for everyone because there's missing context. I'd be happy to continue the conversation if you post again and include all the relevant information (see the top post). Thank you for understanding.

  1. CodeSandbox
  2. User can select a first name and it forces the last name to change. User can select a last name and it does not force the first name to change.
  3. There's a custom hook which returns a piece of state, a select component that updates that state, and the state hook's update method that updates the state programmatically. As demonstrated, I don't always want to use the updater function so I left it to the be last item in returned array.

I believe the code as-is shouldn't error. It does right now on line 35, saying that setLastName should be included in the array. Any thoughts on what to do about that? Am I doing something unexpected?

I totally understand, and I would normally do all that for you, but in my specific case, none of the code is unique to me. It's simply a question about whether the use of a function that is defined outside of the hook (ie. a redux action creator) and used within a hook should require that function to be added as a hook dep.

Happy to create a codesandbox if you still need more info. thx

@joelmoss I think my repro covers your case too.

Yes, a CodeSandbox would still help. Please imagine what it's like to context switch between people's code snippets all day. It's a huge mental toll. None of them look the same. When you need to remember how people use action creators in Redux or some other concept external to React it's even harder. The problem may sound obvious to you but it's not at all obvious to me what you meant.

@gaearon I understand it makes sense, actually it worked for me because why I wanted to achieve was :

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument.

Thanks a lot for the clarification. (and sorry for the off topic)

Here's the code version of what I'm implementing. It don't look like much, but the pattern is 100% identical with my real code.

https://codesandbox.io/s/2x4q9rzwmp?fontsize=14

  • An explanation of the steps a user does and what you expect to see on the screen.

Everything works great in this example! Except the linter issue.

  • An explanation of the intended API of your Hook/component.

I've got several files like this, where I'm executing a function in the effect, but I only want it to run whenever some condition is met - for instance, changing the series Id. I don't want to include the function in the array.

This is what I get from the linter when running it on the sandbox code:

25:5   warning  React Hook useEffect has a missing dependency: 'fetchPodcastsFn'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

My question is: is this how it should behave (either the linter rule, or the hook array rule)? Is there a more idiomatic way of describing this effect?

@svenanders, I'm curious about the reason why you don't want to include fetchPodcastsFn? Is it because you find it change on every render? If it is, you probably want to memoize that function or make it static (in case it doesn't have any parameters)

For one - it's about clarity. When I look at the function, I want to easily understand when it should fire. If I see _one_ id in the array, it's crystal clear. If I see that id and a bunch of functions, it becomes more muddled. I have to spend time and effort, possibly even debugging functions, to understand what's going on.
My functions don't change on runtime, so I don't know if memoizing them would matter (this one in particular is a dispatch action that fires off an epic, eventually resulting in a state change).

https://codesandbox.io/s/4xym4yn9kx

  • Steps

The user accesses a route on the page, but they're not a super user, so we want to redirect them away from the page. The props.navigate is injected via a router library so we don't actually want to use window.location.assign to prevent a page reload.

Everything works!

  • Intended API

I put in the dependencies correctly as in the code sandbox, but the linter is telling me that the dependency list should have props instead of props.navigate. Why is that?

A tweet with screenshots! https://twitter.com/ferdaber/status/1098966716187582464

EDIT: one valid reason this could be buggy is if navigate() is a method that relies on a bound this, in which case technically if anything inside props changes then the stuff inside this will also change.

CodeSandbox: https://codesandbox.io/s/711r1zmq50

Intended API:

This hook allows you to debounce any fast changing value. The debounced value will only reflect the latest value when the useDebounce hook has not been called for the specified time period. When used in conjunction with useEffect, as we do in the recipe, you can easily ensure that expensive operations like API calls are not executed too frequently.

Steps:

The example allows you to search the Marvel Comic API and uses useDebounce to prevent API calls from being fired on every keystroke.

IMO adding "everything" we use in dependencies array is not efficient.

For example consider the useDebounce Hook. In real world usages (at least in ours), the delay will not change after the first render, but we are checking if it's changed or not on every re-render. So, in this hook, the second argument of useEffect is better to be [value] instead of [value, delay].

Please don't think shallow equality checks are extremely cheap. They can help your app when placed strategically but just making every single component pure can actually make your app slower. Tradeoffs.

I think adding everything in dependencies array, has the same (or even worse) performance issue as using pure components everywhere. Since, there are many scenarios in which we know that some of the values we're using will not change, so we shouldn't add them in dependencies array, because as @gaearon said, shallow equality checks are not very cheap and this can make our app slower.

I have some feedback about enabling this rule to automatically work on custom hooks by convention.

I can see in the source code that there is some intent to allow people to specify a regex to capture custom hooks by name:

https://github.com/facebook/react/blob/ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L490-L492

What would the React team think about an additional naming convention for custom hooks with dependency arrays? Hooks already follow the convention of being prefixed by use in order to be detected as a hook by this plugin. The convention I would propose for detecting custom hooks that rely on certain dependencies would be some sort of postfix, something like WithDeps, meaning a full custom hook name could be something like useCustomHookWithDeps. The WithDeps postfix would tell the plugin that the final array argument is one of dependencies.

The plugin could still additionally support regexes, but I think I'd see great benefit by allowing library authors to simply export their custom hooks postfixed with WithDeps rather than forcing library consumers to explicitly configure the plugin for any and all custom hooks 3rd-party or otherwise.

It warning and auto remove of the custom equality check.

const myEqualityCheck = a => a.includes('@');

useCallback(
  () => {
    // ...
  },
  [myEqualityCheck(a)]
);

For useEffect, I don't think the "unnecessary dependency" warning should appear because those "dependencies" will change how the effect fires.

Let's say I have two counters, parent and child:

  • The counters can be incremented/decremented independently.
  • I want to reset the child counter to zero when the parent counter changes.

Implementation:

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);

  useEffect(
    () => {
      setChild(0);
    },
    [parent] // "unnecessary dependency: 'parent'"
  );

The exhaustive-deps rule gives the warning React Hook useEffect has an unnecessary dependency: 'parent'. Either exclude it or remove the dependency array.

screen shot 2019-02-25 at 11 06 40 am

I don't think the linter should be making this suggestion because it changes how the app behaves.

  1. CodeSandbox: https://codesandbox.io/s/ol6r9plkv5
  2. Steps: When parent changes, child resets to zero.
  3. Intention: Instead of flagging the dependency as unnecessary, the linter should recognize that parent changes the behavior of useEffect and shouldn't advise me to remove it.

[EDIT]

As a workaround, I can write something like

const [parent, setParent] = useState(0)
const [child, setChild] = useState(0)
const previousParent = useRef(parent)

useEffect(() => {
  if (parent !== previousParent.current) {
    setChild(0)
  }

  previousParent.current = parent
}, [parent])

But there is a "poetry" to the original snippet that I like.

I think the question comes down to whether we should interpret the dependency array as just a mechanism for performance optimizations vs actual component behavior. The React Hooks FAQ uses performance as an example for why you would use it, but I didn't interpret the example to mean you should only use the dependency array to improve performance, instead I saw it as a convenient way to skip the effect calls in general.

It's actually a pattern I've used a couple of times, to invalidate some internal cache:

useEffect(() => {
  if (props.invalidateCache) {
    cache.clear()
  }
}, [props.invalidateCache])

If I shouldn't be using it in this way, then feel free to just let me know and disregard this comment.

@MrLeebo
What about

  const [parent, setParent] = useState(0);
  const [child, setChild] = useState(0);
  const updateChild = React.useCallback(() => setChild(0), [parent]);

  useEffect(
    () => {
      updateChild();
    },
    [updateChild] 
  );

I wouldn't understand that snippet of you either. The dependency can only be assumed based on your code but it might be a bug. While my proposal doesn't necessarily solve that it makes it at least more explicit.

It looks like this was previously solved via getDerivedStateFromProps? Does How do I implement getDerivedStateFromProps? help?

@nghiepit I hid your comment because you ignored the required checklist in the first post (e.g. CodeSandbox). Please follow the checklist and post again. Thanks.

@eps1lon You would have the exact same warning on useCallback, and I disagree with that pattern in general being an improvement over the original, but I don't want to derail the topic to talk about that. To clarify, I think the unnecessary dependency rule should be relaxed for useEffect or useLayoutEffect specifically, because they can contain effectful logic, but the rule should remain in place for useCallback, useMemo, etc.

I'm running into some of the same issues/mental model questions that @MrLeebo has described here. My gut feeling is that the useEffect dependency rule can't be as strict. I have an incredibly contrived example that I was working on for a basic proof of concept idea. I know this code sucks and the idea isn't particularly useful, but I think it's a good illustration of the problem at hand. I think @MrLeebo expressed the question I am having quite well:

I think the question comes down to whether we should interpret the dependency array as just a mechanism for performance optimizations vs actual component behavior. The React Hooks FAQ uses performance as an example for why you would use it, but I didn't interpret the example to mean you should only use the dependency array to improve performance, instead I saw it as a convenient way to skip the effect calls in general.

https://codesandbox.io/s/5v9w81j244

I'd specifically look at the useEffect hook in useDelayedItems. Right now including the items property in the dependency array will cause a linting error, but removing that property or the array entirely gives you behavior you do not want.

The basic idea of the useDelayedItems hook is given an array of items and a config object (delay in ms, initial page size), I will initially be handed a subset of items depending on my configured page size. After config.delay has passed, the items will now be the full set of items. When it is handed a new set of items, the hook should re-run this "delay" logic. The idea is a very crude and dumb version of delayed rendering of large lists.

Thanks for all the work on these rules, it's been very helpful when developing. Even if my example is of questionable quality, I hope it gives some insight in how these operators may be ab/used.

EDIT: I have added a few clarifying comments inside the codesandbox around behavior intent. I hope the information is sufficient, but let me know if anything is still confusing.

What about a single value that combines other values?

Example: fullName is derived from firstName and lastName. We want to trigger the effect only when fullName changes (like when the user hits "Save") but also want to access the values it composes in the effect

Demo

Adding firstName or lastName to the dependencies would break things since we only want to run the effect after fullName has changed.

@aweary I am not sure what value you are getting from the useEffect prop change indirection. It seems that your onClick should be handling that "effect".

https://codesandbox.io/s/0m4p3klpyw

As far as single values that combine other values, useMemo is probably going to be what you want. The delayed nature of the calculation in your example means it doesn't map exactly 1:1 with your linked behavior.

I will create codesandbox links for these examples and edit this post.

I have an extremely simple rule: if the current tab changed, scroll to the top:
https://codesandbox.io/s/m4nzvryrxj

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

And I got React Hook useEffect has an unnecessary dependency: 'activeTab'. Either exclude it or remove the dependency array

Also, when I was migrating some components, I want to replicate the componentDidMount behavior:

useEffect(() => {
    ...
  }, []);

this rule is complaining hard about this. I am hesitant to add every dependency to the useEffect array.

Finally, if you declare a new inline function before useEffect, like this:
https://codesandbox.io/s/nr7wz8qp7l

const foo = () => {...};
useEffect(() => {
    foo();
  }, []);

You get: React Hook useEffect has a missing dependency: 'foo'. Either include it or remove the dependency array
I am not sure how I feel about adding foo to the dependencies list. In each render foo is a new function and useEffect would always get run?

@ksaldana1 putting the side effect inside the event handler isn't sufficient. That would cause the side effect to occur before the actual update is committed, which might cause the side effect to trigger more often than you want.

It also won't work when using useReducer because the updated state won't be available inside the event handler.

As far as single values that combine other values, useMemo is probably going to be what you want.

If this example used useMemo it would break because useMemo would derive a new fullName every time firstName or lastName changed. The behavior here is that fullName doesn't get updated until the Save button is clicked.

@aweary Would something like this work? https://codesandbox.io/s/lxjm02j50m
I'm very curious to see what the recommended implementation is.

I'm also curious to hear more about a couple things you stated. Can you point me to any more info about these?

Triggering and effect in handler:

that would cause the side effect to occur before the actual update is committed, which might cause the side effect to trigger more often than you want.

Using useReducer state in event handler:

the updated state won't be available.

Thanks!

@bugzpodder kind of unrelated but the scroll call should be inside useLayoutEffect instead of useEffect. Currently there's a noticeable flicker when navigating to the new route

Not sure if this is intentional or not:

When you call a function from props, the linter suggests adding the entire props object as a dependency.

Short version:

useEffect(function() {
  props.setLoading(true)
}, [props.setLoading])

// ⚠️ React Hook useEffect has a missing dependency: 'props'.

Full version: Codesandbox

Trying again... but simpler this time ;)

When passing down a function in the props, or indeed from anywhere, and using that function inside a hook, I get a exhaustive-deps warning.

So I can obviously add the function to the dependency array, but because it is a function and never changes, this is unneccessary right?

-> Sandbox

Hope that is everything you need, but I simply forked @siddharthkp's sandbox as that demonstrated what I meant.

thx.

So I can obviously add the function to the dependency array, but because it is a function and never changes, this is unneccessary right?

This is not correct; functions can change all the time when the parent re-renders.

@siddharthkp

When you call a function from props, the linter suggests adding the entire props object as a dependency.

This is because technically props.foo() passes props itself as this to foo call. So foo might implicitly depend on props. We'll need a better message for this case though. The best practice is always destructuring.

functions can change all the time when the parent re-renders.

Sure, but if the function were defined elsewhere and not from a parent component, it wouldn't ever change.

Sure, but if the function were defined elsewhere and not from a parent component, it wouldn't ever change.

If you import it directly, the lint rule won't ask you to add it to the effect deps. Only if it's in the render scope. If it's in the render scope then the lint rule doesn't know where it's coming from. Even if it's not dynamic today, it could be tomorrow when somebody changes the parent component. So specifying it is the right default. It doesn't hurt to specify it if it's static anyway.

thx @gaearon

This is because technically props.foo() passes props itself as this to foo call. So foo might implicitly depend on props. We'll need a better message for this case though. The best practice is always destructuring.

That answers my question as well. Thank you! 😄

https://codesandbox.io/s/98z62jkyro

So i am creating a library for handling input validation by registering all inputs using an api exposed in a context for each input to register themselves. I created a custom hook called useRegister.

Ex:

 useEffect(
    () => {
      register(props.name, props.rules || []);
      console.log("register");
      return function cleanup() {
        console.log("cleanup");
        unregister(props.name);
      };
    },
    [props.name, props.rules, register, unregister]
  );

When forcing props.rules to be a part of the dependencies, it seems to end up in an infinite render-loop. Props.rules is an array of functions to be registered as validators. I have only detected this issue when providing arrays as dependencies. In my codesandbox, you can see that it loops opening the console.

Just having props.name as dependency makes it work as intended. Enforcing the dependencies will as other pointed out change the behavior of the application, and in this occasion the side-effects are severe.

@bugzpodder

Re: https://github.com/facebook/react/issues/14920#issuecomment-467212561

useEffect(() => {
    window.scrollTo(0, 0);
  }, [activeTab]);

This seems like a legit case. I'm going to relax the warning to allow extraneous deps for effects only. (But not for useMemo or useCallback.)

Also, when I was migrating some components, I want to replicate the componentDidMount behavior:
this rule is complaining hard about this. I am hesitant to add every dependency to the useEffect array.

Sorry, you didn't add any example for that so we lost the opportunity to discuss that. That's exactly why the OP post asks to specify a concrete UI example. You did for the first point but not this one. I'm happy to discuss it when you add a concrete example for it. The specifics really depend on it.

Finally, if you declare a new inline function before useEffect, like this: https://codesandbox.io/s/nr7wz8qp7l

In this case the easy fix is to move doSomething into the effect. Then you don't need to declare it. Alternatively, you can useCallback around doSomething. I'm open to relaxing the rule to allow omitting functions that only use declared deps. But this can get confusing if you have a function calling another function, and add a prop or state into one of those functions. Suddenly all effect deps using it transitively have to be updated. It can get confusing.

I don't know if this is a feature request for a new rule, or something that could be improved about exhaustive-deps

import React from 'react'
import emitter from './thing'

export const Foo = () => {
  const onChange = () => {
    console.log('Thing changed')
  }

  React.useEffect(() => {
    emitter.on('change', onChange)
    return () => emitter.off('change', onChange)
  }, [onChange])

  return <div>Whatever</div>
}

Because the onChange function is created every render, the useEffect hook [onChange] argument is redundant, and might as well be removed:

   React.useEffect(() => {
     emitter.on('change', onChange)
     return () => emitter.off('change', onChange)
-  }, [onChange])
+  })

The linter could detect that, and advise you to delete the array argument.

There have been situations where I have been maintaining a list of array items, only to realise one or more of them were being created and invalidating the hook every render anyway.

Just released [email protected] with a few fixes and better messages for this rule. Nothing groundbreaking but a few cases should be solved. I'll be looking at the rest next week.

I also posted first possible step for omitting "safe" function deps here: https://github.com/facebook/react/pull/14996. (See tests.) If you have ideas about useful heuristics and how to guide people to right fixes please comment on the PR.

@gaearon Excellent idea. This will definitely be useful to have a better style when using hooks 🙏

@gaearon It still doesn't work in case the action comes from prop. Consider this example:

image

In which setScrollTop is a redux action.

In this example in the Slider component, I am using useEffect to wait until the DOM is available so I can mount the noUiSlider component. I therefore pass in [sliderElement] to ensure that the ref is available in the DOM when the effect runs. We server render our components as well, so this also ensures the DOM is available before rendering. The other props that I use in useEffect (i.e. min, max, onUpdate, etc) are constants and therefore I don't see a need for them to be passed in to the effect.

screen shot 2019-03-02 at 5 17 09 pm


Here's the effect as seen in codesandbox:

const { min, max, step } = props;
const sliderElement = useRef();

useEffect(() => {
  if (!sliderElement.current) {
    return;
  }

  const slider = sliderElement.current;
  noUiSlider.create(slider, {
    connect: true,
    start: [min, max],
    step,
    range: {
      min: [min],
      max: [max],
    },
  });

  if (props.onUpdate) {
    slider.noUiSlider.on('update', props.onUpdate);
  }

  if (props.onChange) {
    slider.noUiSlider.on('change', props.onChange);
  }
}, [sliderElement]);

@WebDeg-Brian I can’t help you without a full CodeSandbox demo. Sorry. See the top post.

I posted a bit about the common “functions never change” misconception:

https://overreacted.io/how-are-function-components-different-from-classes/

Not quite the same topic, but relevant to this rule.

Hi @gaearon, Here is the example you asked me to post here (from tweeter) :)

Basically i'm trying to convert my library react-trap to hooks.
This is just a trap for events, outside / inside an element.

My problem is that if useEffect is not depended on the state value (trapped), it sometimes being stale.
I wrote some comments and logs to demonstrate. Look at the useTrap.js file, the comments and logs are in useEffect and preventDefaultHelper functions.

To my knowledge, if a value is not inside useEffect then it shouldn't be part of its dependencies (correct me if i'm wrong).

  1. A CodeSandbox
  2. Steps:
    A user click inside the box to make it active, and outside to make it not active, the user can also click with the right mouse button, though for the first click it shouldn't trigger the context menu (e.preventDefault).
    When i say "first click" i mean the first click that changes the state.
    Given an active box, a right click outside of it will change the state to "not active" and prevent the context menu. another click outside wont affect the state, hence the context menu should appear.

I hope i'm being clear here and the use case is understandable, please let me know if i need to provide more info. Thanks!

Hi @gaearon, I am adding here my custom hook as you suggested me in Twitter! I am struggling to find the right shape that does not skip dependencies.

It is elaborate as an example, I hope I can explain it in a clear and understandable manner.

This is the current state of it: react-async-utils/src/hooks/useAsyncData.ts

Overview of the functionality
Helping the user deal with async calls, resulting data and state of it along the process.

triggerAsyncData updates asyncData state asynchronously according to a getData function that returns a Promise. triggerAsyncData can be invoked both as an effect or "manually" by the hook user.

Challenges

  1. The dependencies of the effect that invokes triggerAsyncData are the intrincate ones. triggerAsyncData is a dependency of the effect, but it is created in every render. Line of thoughts so far:

    1. Just add it as a dependency => But then the effect runs on every render.

    2. Add it as a dependency, but use useMemo/useCallback with triggerAsyncData => useMemo/useCallbackshould only be used for performance optimizations AFAIK.

    3. Scope it inside the effect => Then I cannot return it to the user.

    4. Instead of using triggerAsyncData as a dependency, use the dependencies of triggerAsyncData as dependencies => Best option I found so far. But it breaks "exhaustive-deps" rule.

  2. Every input parameter of the custom hook is/becomes a dependency of our inner effect. So inline functions and literal objects as parameters make the effect run too often.

    1. Leave up to the user the responsibility. They will provide appropriate values, using useMemo/useCallback if required => I'm afraid quite often they won't. And if they do it is quite verbose.

    2. Allow an extra argument for the custom hook to provide the deps of the inputs, and use that instead of inputs themselves => Cool, less verbose, more control for the user. I am using this. But it breaks "exhaustive-deps" rule. (Actually using I am this and falling back to regular deps if extra argument not provided. I find it a powerful pattern).

  3. Poorly managed dependencies for the custom hook generate an infinite async loop because of the inner effect. I have used defensive programming to prevent this, but it adds a "fake"? dependency (asyncData). This breaks the "exhaustive-deps" rule again.

Longer explanation than I wished... but I think it reflects my struggles to use hooks in a proper way. Please let me know if there is something else I can do to make these difficulties clearer.

Thank you for all the hard work here!

Hey @gaearon thanks for all your hard work.

  1. A minimal asynchronous data fetching example CodeSandbox example.

  2. User is expected to see 5 lorem ipsum title strings fetched from json api.

  3. I created custom hook for data fetching with intended API:

const { data, isLoading, isError } = useDataApi('https://jsonplaceholder.typicode.com/posts')

Internals of custom useDataApi hook:

...
  const fetchData = async () => {
    let response;
    setIsError(false);
    setIsLoading(true);

    try {
      response = await fetch(url).then(response => response.json());

      setData(response);
    } catch (error) {
      setIsError(true);
    }

    setIsLoading(false);
  };

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );
...

The problem being this code

  useEffect(
    () => {
      fetchData();
    },
    [url]
  );

where react-hooks/exhaustive-deps fires a warning that I should add fetchData to my dependency array and url should be removed.

If I change this hook into

  useEffect(
    () => {
      fetchData();
    },
    [fetchData]
  );

then it's continuously firing requests and never stopping, which is of course a big problem. I'm not sure if my code is buggy or react-hooks/exhaustive-deps is firing a false positive.

Any help appreciated. Thanks so much.

P.S. I read your comment about useEffect not fit for data fetching, however, react docs claim that Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects which gave me confidence that data useEffect is great for data fetching. So now I'm a bit confused 😕

@jan-stehlik you should wrap fetchData with useCallback. i forked your codesandbox with the necessary change here https://codesandbox.io/s/pjmjxprp0m

Very helpful, thanks so much @viankakrisna !

To my knowledge, if a value is not inside useEffect then it shouldn't be part of its dependencies (correct me if i'm wrong).

I think you're wrong here. Or rather, a bit confused. You are using handleEvent inside your effect. But you don't declare it. That's why values it reads are stale.

Interesting.

You are using handleEvent inside your effect. But you don't declare it. That's why values it reads are stale.

What do you mean by: _"But you don't declare it"_?
It is declared below the effect (same as all other handlers).

Or do you mean because the handler is using the state value, and the effect is attaching the handler then it means the effect is depended on that state value?

Or do you mean because the handler is using the state value, and the effect is attaching the handler then it means the effect is depended on that state value?

Yes, if you use a function you must either declare it in deps (and in that case wrap it with useCallback to avoid recreating it), or everything that function uses.

Ok this is news to me! Thank you for this input @gaearon :)
I just want it to be super clear for me (and others?)...

If an effect is invoking, passing or doing anything with a function, we need to pass to its deps array either:
The function itself OR The variables that this function uses.
If the function is declared inside the function component / custom hook then its advised to wrap it with useCallback so it won't get re-created every time our component or custom hook is running.

I must say I didn't see it on the docs.
Do you think its ok to add it to the _Note_ section?

Note
The array of inputs is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically.

Edit
One more thing, in my example, what are the deps for useCallback when it wraps handleEvent (or any other handlers for that reason). is it the event it self?

I must say I didn't see it on the docs.

The docs say "every value referenced inside the effect function should also appear in the inputs array". Functions are values too. I agree we need to document this better — which is what this thread is all about :-) I'm collecting use cases for a new documentation page dedicated to this.

One more thing, in my example, what are the deps for useCallback when it wraps handleEvent (or any other handlers for that reason). is it the event it self?

Not sure what you mean. It's any values referenced by the function outside of it. Just like in useEffect.

I guess i didn't think this through with functions as dependencies. my mental model was wrong, i was thinking like pass a function as a dependency only if it is came in as prop or argument to my component / hook. Thanks for clarifying this for me.

As for the useCallback, i used it like this:

const memoHandleEvent = useCallback(
    handleEvent
);

and of course passed memoHandleEvent as a dependency for useEffect as well to addEventListener insead of the real handleEvent function. seems to work, hope this is the proper and idiomatic way of doing it.

Note useCallback without second argument doesn't do anything.

Again, a complete sandbox would be necessary. I can't tell if your fix is correct by an incomplete description like this.

Note useCallback without second argument doesn't do anything.

Argg! :grimacing: lol

Again, a complete sandbox would be necessary. I can't tell if your fix is correct by an incomplete description like this.

Aw, this is the same link from above. i just updated it :)

Please don't update the CodeSandbox links :P I need them as they were originally — otherwise I can't test the lint rule on them. Could you please create two separate sandboxes if you have two different solutions? So I can check each one.

Oops Sorry! :P I exceeded the number of sandboxes on my account. let me delete some and i'll create another one (and revert the changes in the original).

@gaearon this is the second link to the solution with useCallback

I have a scenario which I believe is okay, but the linter complains. My sample:

CodeSandbox

What the sample is trying to represent is when a user clicks a button, then a request is made to request new data based on an earlier provided Id and a function. If the Id changes, then it should not request new data; only a new request for reload should trigger a new data request.

The example here is a little contrived. In my real application, React lives in a DIV that is a small part of a much larger web application. The function that is passed in is via Redux and mapDispatchToProps, where an action creation takes the Id and makes an ajax request to fetch data and update the store. The refreshRequest prop is passed in via React.createElement. In my original implementation, I had code that looked like this in the class component:

componentDidUpdate (prevProps) { const { getData, someId, refreshRequest} = this.props; if (prevProps.refreshRequest!== this.props.refreshRequest) { getData(someId); } }

I'm trying to implement the same behaviour with an effect hook. But as written in the sample, the linter complains:

warning React Hook useEffect has missing dependencies: 'getData' and 'someId'. Either include them or remove the dependency array

If I add in everything the linter wants, then if the user clicks either button in the sample useEffect is triggered. But I only want it to be triggered when the Request new data button is pressed.

Hopefully it makes sense. I'd be happy to clarify more, if is something is unclear. Thank you!

I just published [email protected] which has an experimental support for detecting bare function dependencies (which tend to be not very useful without useCallback). Here's a gif:

demo

Would love if y’all could give it a try in your projects and see how it feels! (Please comment on that specific flow in https://github.com/facebook/react/pull/15026.)

I’ll be trying it on examples from this thread tomorrow.

I haven't tried it yet but I wonder if it deals with hoisting. This is just a "minimum example" I came up with in the spot so it's not useful for anything, but I do use hoisted declarations a lot to make it easier to get to see the return statement.

function Component() {
  useEffect(() => {
    handleChange
  }, [handleChange])

  return null

  function handleChange() {}
}

It would be nice if the rule had an option to configure another function to behave like useEffect as far as the linter is concerned. For example, I added this so I can easily use async functions for effects that do an AJAX call - however, like this I lose all exhaustive-deps linting benefits:

const useAsyncEffect = (fn, ...args) => {
    /* eslint-disable react-hooks/exhaustive-deps */
    useEffect(() => {
        fn();
    }, ...args);
};

Edit: Nevermind, I just noticed that this is already possible using the additionalHooks option of the rule.

Hi all,
I have an example https://codesandbox.io/s/znnmwxol7l

The below is what I want:

function App() {
  const [currentTime, setCurrentTime] = React.useState(moment());

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime.format("MMMM")] // <= this proplem [currentTime]
  );

  return (
    <div className="App">
      <h1>Current month: {currentMonth}</h1>
      <div>
        <button
          onClick={() => setCurrentTime(currentTime.clone().add(1, "days"))}
        >
          + 1 day
        </button>
      </div>
      <div>{currentTime.toString()}</div>
    </div>
  );
}

But this is what I get:

  const currentMonth = React.useMemo(
    () => {
      console.log("RUN");
      return currentTime.format("MMMM");
    },
    [currentTime] // <= this proplem
  );

Anytime the currentTime change then currentMonth recompute unnecessary

Or somehow I can do it below:

  const currentMonth = React.useMemo(
    () => {
      return currentTime.format("MMMM");
    },
    [myEqualityCheck(currentTime)]
  );

Sorry if this has been answered already, couldn't see it in the thread above:
The way to run the useEffect hook only on mount is to define an empty inputs array as the second parameter. However when doing that the exhaustive-deps complaints about including those input arguments, which would change the effect to also run on update.
What's the approach for running useEffect only on mount with exhaustive-deps enabled?

@einarq I guess you need to make sure that all the referred values in your on-mount useEffect will never change. That could be achieved using other hooks like useMemo. After that regardless if this ESlint rule adds all the references into array (with autofix) or not, the code will be executed only once.

@einarq As stated elsewhere in the thread, we can't help you without a CodeSandbox. Because the answer really depends on your code.

@nghiepit Your example doesn't really make sense to me. If your input is currentTime.format("MMMM") then useMemo doesn't optimize anything for you because you already computed it. So you're just computing it twice unnecessarily.

Is it possible to specify which argument index is the callback on additionalHooks option? I see that we are assuming right now in code that it would be the first https://github.com/facebook/react/blob/9b7e1d1389e080d19e71680bbbe979ec58fa7389/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1051-L1081

Not currently possible. Our general guidance is that custom Hooks should prefer not to have a deps argument because it becomes very difficult to think about how deps compose. If you take a function you might wanna ask users to useCallback instead.

@CarlosGines Can you please make a CodeSandbox, as requested in the OP post. I'm asking this for a reason — otherwise it's hard for me to verify changes. Especially when it's written in TypeScript.

I just published [email protected] with hopefully more useful messages and smarter heuristics. Please give it a try on your projects before it goes stable.

I expect that with most examples in this thread, it should give reasonable advice that should help you fix the problems.

should that be [email protected]?

Yes.

Actually just published [email protected] with a couple of small changes.

@gaearon Did you managed to get eslint to work inside the codesandbox by any chance?

@einarq maybe something like this would this work?

const useDidMount = fn => {
  const callbackFn = useCallback(fn)
  useEffect(() => {
    callbackFn()
  }, [callbackFn])
}

I'm actually embarrassed to say that i can't get the plugin to work on a simple CRA app (based on my snippet).
here is the forked repo from codesandbox.

I've seen this note in the docs:

Note: If you're using Create React App, please wait for a corresponding release of react-scripts that includes this rule instead of adding it directly.

But is there no way to test it with CRA at the moment? 👂

Yeah you'd have to eject and add it to the ESLint config until we add it there. You can eject in a branch and not merge it. :-)

Hi,

first of all: Thanks a lot for working so closely with the community and the amazing work you're doing in general!

We've got an issue with a custom hook that we built around the Fetch API. I've created a Codesandbox to demonstrate the issue.

Example on Codesandbox

https://codesandbox.io/s/kn0km7mzv

Note: The issue (see below) results in an infinite loop and I don't want to DDoS jsonplaceholder.typicode.com which I'm using to demonstrate the issue. Therefore, I've included a simple request limiter using a counter. This isn't required to demonstrate the issue but it felt wrong to fire an unlimited amount of requests to this great project.

Explanation

The idea is to make it easier to handle the 3 possible states of an API request: Loading, Success and Error. Thus we've build a custom hook useFetch() which returns 3 properties isLoading, response and error. It makes sure that eitherresponseorerroris set and updatesisLoading. As the name implies, it uses theFetch` API.

To do that, it uses 3 useState hooks:

  const [isLoading, setIsLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

and a useEffect hook:

useEffect(
    () => {
      fetch(url, fetchConfig)
        .then(/* Handle fetch response... See Codesandbox for the actual implementation */)
    },
    [url]
  );

It works fine as long as the dependencies array of useEffect only contains [url]. If we add fetchConfig (= [url, fetchConfig]), it results in an infinite loop. For our particular use case, it would be sufficient to only rerun the effect when the url changes but the linter doesn't allow do use only [url] (tested with v1.4.0 and v1.5.0-beta.1).

At the end of the custom hook, the 3 state variables are returned as an object:

  return {
    error,
    isLoading,
    response
  };

I'm not sure if this is the right place to ask for guidance on this issue as the linting suggestion makes sense, I guess. Sorry if it's not.

What if fetchConfig changes? Why do you expect it to be static?

All right. I released [email protected] with the last week’s fixes and improvements.
This thread has been immensely helpful in finding different patterns that cause problems.

Thank you so much to all of you. (Especially those who supplied sandboxes. :-)


I won’t respond to everyone personally in detail because it’s a lot of cases.

Instead, we’ll be writing up common recipes and gotchas in the next few days, and link to them from the docs. I’ll make sure that every common pattern in this thread is covered (or ask to follow up in a separate issue for rare patterns).

Once the examples are published, I’ll comment here and edit every comment containing a CodeSandbox by appending a link to the relevant answer / example that demonstrates the right fix. I hope this will help you and future readers.

Cheers!

❤️

@timkraut you should be able to add fetchConfig on the deps, and the component should wrap it with memo so the reference stays.
An example: https://codesandbox.io/s/9l015v2x4w


My problem with this is that now the component needs to be aware about implementation details of the hook...

I'm not sure if there this thread is still open to example discussion, but I'm still wrapping my head around best practices. My question is around controlling when the useEffect hook fires with the dependency array and using the values at that time vs requiring declaring a separate ref to get around the lint rule.

Example

https://codesandbox.io/s/40v54jnkyw

In this example, I am trying to autosave input values periodically.

Explanation

Every 5 seconds, the code is trying to autosave the current values (just print them out to the screen for now). The linter would require all input values be included in the dependency array which would change how often the effect would fire.

  useEffect(
    () => {
      if (autoSaveTick % 5 === 0) {
        setAutosaveValue(
          `${input1Value.value}, ${input2Value.value}, ${input3Value.value}`
        );
      }
    },
    [autoSaveTick]
  );

The alternative I see is having an implementation similar to the defined useTick and useInterval hooks in the example, where there is a callback assigned to a ref. It seems like that would cause unnecessary function redefinitions just for the sake of aligning with the lint rule.

I'm looking for best practices in writing an effect that runs when one variable changes (Variable A) that uses other variable values (Variable B & Variable C) at the time of the previous variable change (Variable A).

What if fetchConfig changes? Why do you expect it to be static?

I'd be fine to add it to the dependencies array. In our particular business case, this can never happen but I guess it's a good idea to add it anyway.

My problem with this is that now the component needs to be aware about implementation details of the hook...

That's exactly the issue we have, too :/ In our implementation, we've even use an additional property to make it easier to set the body so we would have to use 2 useMemo() calls whenever we use this hook. Not sure how to overcome this limitation, yet. If you have an idea, let me know!

How can you run a useEffect which has dependencies only once without the rule complaining when passing an empty array?

Say, something like:

useEffect(() => { init({ dsn, environment}) }, [])

My problem with this is that now the component needs to be aware about implementation details of the hook...

I wouldn’t say these are implementation details. It’s your API. If an object is passed we assume it can change any time.

If in practice it’s always static, you can make it an argument to a Hook factory. Like createFetch(config) that returns useFetch(). You call the factory at the top level.

I understand it’s a bit weird. We have a similar problem with the useSubscription that we’re working on. But since it’s a common problem, this might mean useMemo actually is a legit answer and people should just get used to doing it for these cases.

In practice — we’ll be looking more at this in the future. But you shouldn’t treat a potentially dynamic object as a static one because then a user can’t change it.

@asylejmani You haven’t provided any details about your use cases, as requested in the top post. Why do you expect that someone can provide an answer your question?

The point of the rule is to tell you that environment and dsn can change over time and your component should handle that. So either your component is buggy (because it doesn’t handle changes to those values) or you have some unique case where it doesn’t matter (in which case you should add a lint rule ignore comment explaining why).

In both cases it’s not clear what you are asking. The rule complains that things can change, and you don’t handle that change. Without a full example, you alone can answer why you think handling it is not necessary.

@gaearon sorry for not being clearer (it always sounds clear in one's head) :) my question is more like how would you achieve componentDidMount with useEffect.

I was under the impression than an empty array does that, but the rule is telling me to always include dependencies in the array.

@asylejmani I think the biggest gotcha with class life cycle methods like componentDidMount is that we tend to think of it as an isolated method, but in fact it's part of a flow.
If you reference something in componentDidMount you will most probably need to handle it in componentDidUpdate as well, or your component may get buggy.
This is what the rule is trying to fix, you need to handle values over time.

  1. component mounted, do something with a prop
  2. component updated (eg: prop value has changed), do something with the new prop value

Number 1 is where you put the logic in componentDidMount / useEffect body
Number 2 is where you put logic in componentDidUpdate / useEffect deps

The rule is complaining you are not doing the number 2 part of the flow

@gaearon sorry for not being clearer (it always sounds clear in one's head) :) my question is more like how would you achieve componentDidMount with useEffect.

I was under the impression than an empty array does that, but the rule is telling me to always include dependencies in the array.

I think me and @asylejmani are on the same page here, but I guess what you are saying @gaearon is that we are probably wrong to only run the effect on mount if we actually have dependencies.
Is that a fair statement? I guess I'm thinking that providing an empty array is sort of like saying "I know what I'm doing", but I get why you want to still have the rule in place.

Sorry for not providing a sandbox yet. I started the other night with a Create React App example, couldn't find out how to run eslint on the sandbox, and then lost the sandbox when reloading the browser without saving first (assumed CodeSandbox temp stored, my bad).
Then I had to go to bed, and haven't had time since.

In any case, I get what you are saying, and that under normal scenarios it is probably best to include those dependencies and not assume it's enough to run on mount only.

There are probably valid use-cases for that too though, but it's a bit hard to explain or come up with a good example, so I'll live with disabling the rule inline when needed.

@asylejmani is your use case similar to https://github.com/facebook/react/issues/14920#issuecomment-466378650? I don't think it's possible for the rule to understand the scenario in this case, so we'll just need to manually disable it for that type of code. In all other cases the rule works as it should.

Not sure if this makes sense, but one scenario that's quite common for me is something like this:

useEffect(() => {
    if(!dataIsLoaded) { // flag from redux
        loadMyData(); // redux action creator
    }
}, []);

Both of these dependencies are coming from redux. The data (in this case) should only be loaded once, and the action creator is always the same.

This is specific to redux, and your eslint rule cannot know this, so I get why it should warn. Still wondering if providing an empty array should perhaps just disable the rule? I like that the rule tells me about missing deps if I provided some but not all, or if I didn't provide any at all. Empty array means something different to me. But that might just be me :)

Thanks for all your hard work! And for making our lives as developers better :)

My use case is much simpler and I can of course add all the dependencies and it will still work the same, however I was under the impression that the rule will "warn" when you have some dependencies but missing others.

@einarq's use case is something I used a few times, for example on "componentDidMount" load the data if there is none (from redux or whatever).

I also agree that in these cases disabling the rule inline is the best choice. That case you know exactly what you're doing.

I believe my whole confusion was [] vs [some], and of course thanks @gaearon for the awesome work :)

I think me and @asylejmani are on the same page here, but I guess what you are saying @gaearon is that we are probably wrong to only run the effect on mount if we actually have dependencies. Is that a fair statement?

Yes. If your component doesn't handle updates to a prop, it is usually buggy. The design of useEffect forces you to confront it. You can of course work around it, but the default is to nudge you to handle these cases. This comment explains it well: https://github.com/facebook/react/issues/14920#issuecomment-470913287.

Both of these dependencies are coming from redux. The data (in this case) should only be loaded once, and the action creator is always the same.

If it's the same then including it in the dependencies won't hurt you. I want to emphasize it — if you're sure your dependencies never change then there is no harm in listing them. However, if later it happens that they change (e.g. if a parent component passes different function depending on state), your component will handle that correctly.

Still wondering if providing an empty array should perhaps just disable the rule?

No. Providing an empty array and then wondering why some props or state is stale is literally the most common mistake.

>

Makes a ton of sense, thanks

On 8 Mar 2019, at 15:27, Dan Abramov notifications@github.com wrote:

I think me and @asylejmani are on the same page here, but I guess what you are saying @gaearon is that we are probably wrong to only run the effect on mount if we actually have dependencies. Is that a fair statement?

Yes. If your component doesn't handle updates to a prop, it is usually buggy. The design of useEffect forces you to confront it. You can of course work around it, but the default is to nudge you to handle these cases. This comment explains it well: #14920 (comment).

Both of these dependencies are coming from redux. The data (in this case) should only be loaded once, and the action creator is always the same.

If it's the same then including it in the dependencies won't hurt you. I want to emphasize it — if you're sure your dependencies never change then there is no harm in listing them. However, if later it happens that they change (e.g. if a parent component passes different function depending on state), your component will handle that correctly.

Still wondering if providing an empty array should perhaps just disable the rule?

No. Providing an empty array and then wondering why some props or state is stale is literally the most common mistake.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@aweary

That would cause the side effect to occur before the actual update is committed, which might cause the side effect to trigger more often than you want.

I'm not sure what this means. Can you provide an example?

We did a pass over these with @threepointone today. Here's a summary:

Fixed in the Lint Rule

Extraneous useEffect dependencies

The rule doesn't prevent you from adding "extraneous" deps to useEffect anymore since there are legit scenarios.

Functions in the same component but defined outside of the effect

Linter doesn't warn for cases where it's safe now, but in all other cases it gives you better suggestions (such as moving the function inside the effect, or wrapping it with useCallback).

Worth Fixing in User Code

Resetting state on props change

This doesn't produce lint violations anymore but the idiomatic way to reset state in response to props is different. This solution will have an extra inconsistent render so I'm not sure it's desirable.

"My non-function value is constant"

Hooks nudge you towards correctness wherever possible. If you do specify the deps (which in some cases you can omit), we strongly recommend to include even the ones that you think won't change. Yes, in this useDebounce example the delay is unlikely to change. But it's still a bug if it does, but the Hook can't handle it. This shows up in other scenarios, too. (E.g. Hooks are much more compatible with hot reloading because every value is treated as dynamic.)

If you absolutely insist that a certain value is static, you can enforce it.
The safest way is to do so explicitly in your API:

const useFetch = createFetch({ /* config object */});
const useDebounce = createDebounce(500);
const FormInput = createInput({ rules: [emailValidator, phoneValidator] });

Then it clearly can’t change unless you put it inside render. (Which would not be idiomatic usage of your Hook.) But saying that <Slider min={50} /> can never change isn't really valid — somebody could easily change it to <Slider min={state ? 50 : 100} />. In fact, someone could do this:

let slider
if (isCelsius) {
  slider = <Slider min={0} max={100} />
} else {
  slider = <Slider min={32} max={212} />
}

If someone switches isCelsius in state, a component assuming min never changes will fail to update. It's not obvious in this case that the Slider will be the same one (but it will be because it has the same position in the tree). So this is a major footgun in terms of making changes to the code. A major point of React is that updates render just like the initial states (you can't usually tell which is which). Whether you render prop value B, or whether you go from prop value A to B — it should look and behave the same.

While this is unadvisable, in some cases, the enforcement mechanism could be a Hook that warns when the value changes (but provides the first one). At least then it's more likely to be noticed.

function useMyHook(a) {
  const initialA = usePossiblyStaleValue(a);
  // ...
}

function usePossiblyStaleValue(value) {
  const ref = useRef(value);

  if (process.env.NODE_ENV !== 'production') {
    if (ref.current !== value) { // Or your custom comparison logic
      console.error(
        'Unlike normally in React, it is not supported ' +
        'to pass dynamic values to useMyHook(). Sorry!'
      );
    }
  }

  return ref.current;
}

There may also be a legitimate case where you just can't handle an update. Such as if the lower level API doesn't support it, like a jQuery plugin or a DOM API. In that case warning is still appropriate so that the consumer of your component understands it. Alternatively, you can make a wrapper component that resets the key on incompatible updates — forcing a clean remount with fresh props. That's probably preferable for leaf components like sliders or checkboxes.

"My function value is constant"

First of all, if it's constant and hoisted to top level scope then the linter won't complain. But that doesn't help with things coming from props or context.

If it truly is constant then specifying it in deps doesn't hurt. Such as the case where a setState function inside a custom Hook gets returned to your component, and then you call it from an effect. The lint rule isn't smart enough to understand indirection like this. But on the other hand, anyone can wrap that callback later before returning, and possibly reference another prop or state inside it. Then it won’t be constant! And if you fail to handle those changes, you’ll have nasty stale prop/state bugs. So specifying it is a better default.

However, it is a misconception that function values are necessarily constant. They are more often constant in classes due to method binding, although that creates its own range of bugs. But in general, any function that closes over a value in a function component can't be considered constant. The lint rule is now smarter about telling you what to do. (Such as moving it inside the effect — the simplest fix — or wrapping it with useCallback.)

There is a problem on the opposite spectrum of this, which is where you get infinite loops (a function value always changes). We catch that in the lint rule now when possible (in the same component) and suggest a fix. But it's tricky if you pass something several levels down.

You can still wrap it in useCallback to fix the issue. Remember that technically it is valid for a function to change, and you can’t ignore this case without risking bugs. Such as onChange={shouldHandle ? handleChange : null} or rendering foo ? <Page fetch={fetchComments /> : <Page fetch={fetchArticles /> in the same spot. Or even fetchComments which closes over the parent component state. That can change. With classes, its behavior will change silently but the function reference will stay the same. So your child will miss that update — you don't really have an option outside of passing more data to the child. With function components and useCallback, the function identity itself changes — but only when necessary. So that's a useful property and not just a hindrance to avoid.

We should add a better solution for detecting infinite async loops. That should mitigate the most confusing aspect. We might add some detection for this in the future. You could also write something like this yourself:

useWarnAboutTooFrequentChanges([deps]);

This is not ideal and we’ll need to think about handling this gracefully more. I agree cases like this are pretty nasty. The fix without breaking the rule would be to make rules static, e.g. by changing the API to createTextInput(rules), and to wrap register and unregister into useCallback. Even better, remove register and unregister, and replace them with a separate context where you put dispatch alone. Then you can guarantee you’ll never have a different function identity from reading it.

I'd add that you probably want to put useMemo on the context value anyway because the provider does a lot of calculation that would be sad to repeat if no new components registered but its own parent updated. So this kind of puts the problem you might have not noticed otherwise more visible. Although I agree we need to make it even more prominent when this happens.

Ignoring function dependencies completely leads to worse bugs with function components and Hooks because they would keep seeing stale props and state if you do that. So try not to, when you can.

Reacting to Compound Value Changes

It's weird to me why this example uses an effect for something that's essentially an event handler. Doing the same "log" (I assume it could be a form submit) in an event handler seems more fitting. This is especially true if we think about what happens when component unmounts. What if it unmounts right after the effect was scheduled? Things like form submission shouldn't just "not happen" in that case. So it seems like effect might be a wrong choice there.

That said you can still do what you tried — by making the fullName be setSubmittedData({firstName, lastName}) instead, and then [submittedData] is your dependency, from which you can read firstName and lastName.

Integrating with Imperative/Legacy Code

When integrating with imperative things like jQuery plugins or raw DOM APIs, some nastiness may be expected. That said I'd still expect you to be able to consolidate the effects a bit more in that example.


Hope I didn't forget anyone! Let me know if I did or if something's unclear. We'll try to turn the lessons from this into some docs soon.

@gaearon , thanks for taking the time to dive into the issues and summarizing action items into different categories. You missed my sample (#14920 (comment) by @trevorgithub) in your closing summary comment. (I certainly appreciate there was a lot of feedback from a lot of people; I think my original comment got lost in the hidden items section somewhere in the middle of the issue comments).

I'm assuming my sample would fall under 'Integrating with Imperative/Legacy Code', although maybe other categorie(s) as well?

In the case of 'Integrating with Imperative/Legacy Code' issues, it sound like there may not be a lot that can be done. In those cases, how would one ignore this warning? I guess:
// eslint-disable-line react-hooks/exhaustive-deps

Sorry I missed this one.

If you receive some data via props but don’t want to use those props until some explicit change, it sounds like a correct way to model it would be to have derived state.

You’re thinking about it as “I want to ignore a change to a prop until another prop”. But you can also think about it as “My component has fetching function in the state. It’s updated from a prop when another prop changes.”

Generally derived state is not recommended but here it seems like what you want. The simplest way to implement this would be something like:

const [currentGetData, setCurrentGetData] = useState(getData);
const [prevRefreshRequest, setPrevRefreshRequest] = useState(refreshRequest);

if (prevRefreshRequest !== refreshRequest) {
  setPrevRefreshRequest(refreshRequest);
  setCurrentGetData(getData);
}

useEffect(() => {
  currentGetData(someId);
}, [currentGetData, someId]);

You’d also need to put useCallback around getData that you’re passing down.

Note that in general the pattern of passing down async functions for fetching seems shady to me. I guess in your case you use Redux so that makes sense. But if async function was defined in a parent component, it would be suspicious because you would likely have race conditions. Your effects don’t have a cleanup so how can you know when the user picks a different ID? There’s a risk of requests arriving out of order and setting the wrong state. So that’s just something to keep in mind. If data fetching is in the component itself then you can have an effect cleanup function that sets an “ignore” flag to prevent a setState from the response. (Of course moving data to an external cache is often a better solution — that’s how Suspense will work too.)

Doing the same "log" (I assume it could be a form submit) in an event handler seems more fitting.

@gaearon the issue I see is that doing it in the event handler means the side effect occurs before the update is committed. There isn’t a strict gauruntee that the component will successfully re-render as a result of that event, so doing it in the event handler can be premature.

For example, if I want to log that the user has successfully submitted a new search query and is viewing the results. If something goes wrong and the component throws, I wouldn’t want that log event to have occurred.

Then there’s the case where this side effect might be async so using useEffect gives you the cleanup function.

There’s also the problem of useReducer, where the value I might want to log won’t be available in the event handler. But I think that’s already on y’alls radar 🙂

In either case, the approach you recommend is likely sufficient. Store the composed state in a form where you can still access the individual values it composes.

I have a convenience hook for wrapping functions with extra parameters and passing them down. It looks like this:

function useBoundCallback(fn, ...bound) {
  return useCallback((...args) => fn(...bound, ...args), [fn, ...bound]);
}

(it's actually a little more complicated because it has some extra features but the above still shows the relevant problem).

The use case is when a component needs to pass a property down to a child, and wants the child to have a way to modify that specific property. For example, consider a list where each item has an edit option. The parent object passes a value to each child, and a callback function to invoke if it wishes to edit the value. The child does not know which item ID it is showing, so the parent must modify the callback to include this parameter. This could be nested to arbitrary depth.

A simplified example of this flow is shown here: https://codesandbox.io/s/vvv36834k5 (clicking "Go!" shows a console log which includes the path of components)


The problem is that I get 2 linter errors with this rule:

React Hook (X) has a missing dependency: 'bound'. Either include it or remove the dependency array

React Hook (X) has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies

Changing it to use a list of parameters (i.e. not using the spread operator) would destroy the memoisation, because the list is created with each invocation even if the parameters are identical.


Thoughts:

  • is it worth showing the first error when the second also applies?
  • is there any way I can disable this rule specifically for places where the spread operator is used?
  • if the only use of a variable inside a function is with the spread operator, it is safe to use the spread operator in the dependencies, and since the rule is already detecting this, surely it should just allow it. I realise there are more complex cases which are harder to solve with static analysis, but this seems like an easy win for a relatively common use of spread.

Hi @gaearon, I just got this warning, which I have not found discussed anywhere:

Accessing 'myRef.current' during the effect cleanup will likely read a different ref value because by this time React has already updated the ref. If this ref is managed by React, store 'myRef.current' in a variable inside the effect itself and refer to that variable from the cleanup function.

I find this message a bit confusing. I guess it tries to warns me that the ref current value at cleanup can be different from the value at the effect body. Right?
If that is the case, and being aware of it, is it safe/legit to ignore this warning?

My case, if interesting: CodeSandbox
Context: some custom data fetching hook. I use a counter in a ref to prevent both race conditions and updates on unmounted components.

I think I could circumvent this warning by hiding the ref read inside a function, or creating another boolean ref for the cleanup case. But I find unnecesarily verbose if I can just ignore this warning.

@aweary

For example, if I want to log that the user has successfully submitted a new search query and is viewing the results. If something goes wrong and the component throws, I wouldn’t want that log event to have occurred.

Yeah that sounds like a niche use case. I think when most people want "side effects" they mean form submission itself — not the fact that you viewed a submitted form. In that case the solution I provided seems fine.

@davidje13

is it worth showing the first error when the second also applies?

Please file a new issue for proposals to change the lint rule.

is there any way I can disable this rule specifically for places where the spread operator is used?

You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.

if the only use of a variable inside a function is with the spread operator, it is safe to use the spread operator in the dependencies, and since the rule is already detecting this, surely it should just allow it.

File a new issue pls.

@CarlosGines

I find this message a bit confusing. I guess it tries to warns me that the ref current value at cleanup can be different from the value at the effect body. Right?

Yes.

If that is the case, and being aware of it, is it safe/legit to ignore this warning?

Ummm.. not if that leads to a bug. 🙂

Context: some custom data fetching hook. I use a counter in a ref to prevent both race conditions and updates on unmounted components.

Yeah maybe this use case is legit. File a new issue to discuss pls?

I'm going to lock this issue since we got enough feedback and it has been incorporated.

Common questions & answers: https://github.com/facebook/react/issues/14920#issuecomment-471070149

If you want a deep dive on useEffect and dependencies, it's here: https://overreacted.io/a-complete-guide-to-useeffect/

We'll be adding more stuff to docs soon too.

If you want to change something in the rule or aren't sure your case is legit, file a new issue.

Was this page helpful?
0 / 5 - 0 ratings