Page
Library
Module
Module type
Parameter
Class
Class type
Source
A ppx that validates React's Rules of Hooks at compile time.
Order of Hooks validation:
[@react.component] functions or custom hooksHere 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 | ).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.opam install react-rules-of-hooks-ppxAdd the ppx into the dune files
(preprocess (pps reason-react react-rules-of-hooks-ppx))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](preprocess (pps reason-react react-rules-of-hooks-ppx -disable-exhaustive-deps))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](preprocess (pps reason-react react-rules-of-hooks-ppx -disable-order-of-hooks))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.
Feel free to use it and report any unexpected behaviour in the bug tracker
Thanks to @jchavarri