Skip to content

ml-in-barcelona/react-rules-of-hooks-ppx

Repository files navigation

react-rules-of-hooks-ppx

STATUS: This project isn't complete and might give false-positives. Read more

This package is a no-op ppx rewriter. It is used as a 'lint' to enforce React's Rules of Hooks.

  • Exhaustive dependencies in useEffect
  • Order of Hooks
    • Hooks shoudn't be called in different order
    • Only Call Hooks at the Top Level

Why

One of the points of using Reason or ReScript is to have a compiler that warns about issues with your code, where functions expect different types from the given ones. That's Type-checking and works amazingly well, but there are some cases where even with the right types, the runtime of your program can cause issues. Very commonly in side-effects. I found this case while working with ReasonReact.

ReasonReact and useEffect hooks is one of those cases, where types ensures that the functions are called correctly, but they have certain rules that aren't cacheable at the type-system. Not following those rules might cause some unexpected bug or even a run-time error.

This package solves this problem, lints the use(Layout)Effect from your react.components based on the React's Rules of Hooks

Status

It's a proof of concept on re-creating the ESLint plugin, and it's very restrictive and not always useful in the Reason/ReScript context. Values are immutable by default, so functions or Modules from the exterior of a useEffect will not change at anytime during runtime. The only benefit is where state/props values are missing, which is a great addition to check your useEffects.

Install

npm install react-rules-of-hooks-ppx --save-dev
# or
yarn add react-rules-of-hooks-ppx --dev

Add the ppx on the BuckleScript config (bsconfig.json)

"ppx-flags": [
  "react-rules-of-hooks-ppx/Bin.exe"
]

You can disable globally both rules passing parameters to the ppx:

Disable "Exhaustive dependencies in useEffect"

"ppx-flags": [
  ["react-rules-of-hooks-ppx/Bin.exe", "-exhaustive-deps"]
]

Disable "Order of Hooks"

"ppx-flags": [
  ["react-rules-of-hooks-ppx/Bin.exe", "-order-of-hooks"]
]

Issues

Feel free to use it and report any unexpected behaviour in the issue section

Demo

Here we have a dummy react component:

[@react.component]
/* Recives a prop called "randomProp" */
let make = (~randomProp) => {
  let (show, setShow) = React.useState(() => false);

  /* We have a useEffect that re-runs each time that "show" changes it's value, and we want to update "show" when randomProp is true. */
  React.useEffect1(
    () => {
      /* Since this effect relies on "randomProp" and misses ont the dependency array, will cause undesired behaviour. */
      if (randomProp) {
        setShow(prevShow => !prevShow);
      }
      None;
    },
    [|show|],
  );

  <div />;
};

Produces the following error:

 6 | ..React.useEffect1(
 7 |     () => {
 8 |       if (randomProp) {
 9 |         setShow(_ => !show);
10 |       }
...
13 |       None;
14 |     },
15 |     [|show|],
16 |   ).

Error: ExhaustiveDeps: Missing 'randomProp' in the dependency array

Acknowledgements

Thanks to @jchavarri