package react-rules-of-hooks-ppx

  1. Overview
  2. Docs

doc/README.html

react-rules-of-hooks-ppx

A ppx that validates React's Rules of Hooks at compile time.

Features

  • Exhaustive dependencies in useEffect, useCallback, useMemo, etc.
  • Order of Hooks validation:

    • Hooks can't be called conditionally
    • Hooks must be called at the top level
    • Hooks can only be called from [@react.component] functions or custom hooks

How it works

Exhaustive dependencies

Here we have a dummy react component:

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

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

  <div />;
};

With this ppx, it will produce the following warning:

 6 |   React.useEffect1(
 7 |     () => {
 8 |       if (randomProp) {
 9 |         setShow(_ => !show);
10 |       }
...
13 |       None;
14 |     },
15 |     [|show|],
         ^^^^^^^^
         Error (warning 22): exhaustive-deps: Missing 'randomProp' in the dependency array.
         To suppress this warning, add [@disable_exhaustive_deps] before the expression
16 |   ).

Order of hooks

Hooks must be called at the top level of your component. Calling hooks inside conditionals, loops, or nested functions will produce an error that fails the build:

[@react.component]
let make = (~condition) => {
  if (condition) {
    let (state, _) = React.useState(() => 0); /* That's wrong */
    ();
  };
  <div />;
};

This will produce the following error:

3 |   if (condition) {
4 |     let (state, _) = React.useState(() => 0);
                         ^^^^^^^^^^^^^^^^^^^^^^^^
Error: Hooks can't be called conditionally and must be called at the top-level of your component. Move this hook call outside of conditionals, loops, or nested functions.

Install

opam install react-rules-of-hooks-ppx

Add the ppx into the dune files

(preprocess (pps reason-react react-rules-of-hooks-ppx))
Suppress warnings locally

If you need to suppress an exhaustive deps warning for a specific case (e.g., you intentionally want to omit a dependency), use the [@disable_exhaustive_deps] attribute.

In Reason (.re files):

[@disable_exhaustive_deps]
React.useEffect1(() => {
  /* ... */
  None
}, [|dep|]);

In OCaml (.ml files):

(React.useEffect1 (fun () ->
  (* ... *)
  None
) [|dep|])[@disable_exhaustive_deps]
Disable "Exhaustive dependencies in useEffect" on the library
(preprocess (pps reason-react react-rules-of-hooks-ppx -disable-exhaustive-deps))
Suppress order-of-hooks warnings locally

If you need to suppress an order-of-hooks warning for a specific case (e.g., test utilities, switch%platform or intentional dynamic hook usage), use the [@disable_order_of_hooks] attribute.

In Reason (.re files):

if (condition) {
  [@disable_order_of_hooks]
  useMyHook();
};

In OCaml (.ml files):

if condition then
  (useMyHook ())[@disable_order_of_hooks]
Disable "Order of Hooks" on the library
(preprocess (pps reason-react react-rules-of-hooks-ppx -disable-order-of-hooks))
Enable automatic corrections for missing dependencies

This ppx can generate .ppx-corrected files with suggested fixes for missing dependencies. This integrates with dune's promotion workflow, allowing you to review and accept the suggested changes.

(preprocess (pps reason-react react-rules-of-hooks-ppx -corrections))

When enabled, running the build will show a diff with the suggested fix:

[@react.component]
let make = (~dep1) => {
  React.useEffect0(() => {
    Js.log(dep1);
    None;
  });
  <span />;
};
-  React.useEffect0(() => {
+  React.useEffect1(() => {
    Js.log(dep1);
    None;
-  });
+  }, [| dep1 |]);

You can then run dune promote to accept the corrections.

Issues

Feel free to use it and report any unexpected behaviour in the bug tracker

Acknowledgements

Thanks to @jchavarri