Max RozenMax Rozen
TwitterArticlesNewsletter

How the React Hooks ESLint plugin saved me hours debugging useEffect

Ever find yourself chasing weird bugs in React, like sometimes it seems as though your components fetch all of the data they need, and sometimes they don’t?

Chances are you’ve probably got yourself a race condition.

That’s the special insight I’m giving you after spending several days at work chasing weird behaviour in one of our React apps, that could only be reproduced once in 75 page refreshes.

In case you’re wondering, here’s some backstory.

Imagine your web app needs to make calls to several APIs to get the information it needs to render your component:

useEffect(() => {
  // assume these API calls just write to a global state store
  // and the input state is also coming from that global state store
  getDataFromFirstAPI(someState);
  getDataFromSecondAPI(someOtherState);
  getDataFromThirdAPI(someThirdState);
}, [someState, someOtherState]);

What would happen is that either of getDataFromFirstAPI or getDataFromSecondAPI could mutate the value of someThirdState (as a feature of our particular implementation), making calls to getDataFromThirdAPI use stale data.

Sharp readers will notice the fix pretty quickly: someThirdState is missing from the dependency array, but if your component was complex and had several useEffect calls already, chances are you probably wouldn’t notice it. Especially if several of those useEffect calls also have missing dependencies.

In fact, create-react-app would only tell you about it via a warning, not an error. In our case, the warning would be buried deep in a stack of noisy logs (as part of the review into this type of bug, as a team we decided to make it an error, but it’s up to you and your team).

Turns out there’s an eslint rule specifically for this class of bug: react-hooks/exhaustive-deps, AND it’s part of a package maintained by the React team.

That’s where eslint-plugin-react-hooks comes in. You already have it if you use create-react-app, but chances are you don’t have it if you’ve decided to go with a custom webpack config.

You add it via npm/yarn:

yarn add eslint-plugin-react-hooks -D
# OR
npm i eslint-plugin-react-hooks -D

Afterwards, you can add it to your ESLint config like so:

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies  }
}

Now every time you hit save in your codebase with missing dependencies in your useEffects, you’ll have a similar warning/error to this show up:

Compiled with warnings.

./src/App.js
  Line 16:6:  React Hook useEffect has a missing dependency: 'someThirdState'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.

Since enabling the react-hooks/exhaustive-deps rule, I’ve already saved myself hours of head scratching and general outrage by saving myself from committing buggy code several times.

Do yourself a favour and check if you’ve got the rule enabled.

Do you struggle to keep up with best practices in React?

I send a single email weekly with an article like this one to help improve the quality of your React apps. Lots of developers like them, and I'd love to hear what you think as well. You can always unsubscribe.

    Join 158 React developers who signed up last month!

    rssTwitterGitHub

    © 2020 Max Rozen. All rights reserved.