package react-rules-of-hooks-ppx
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=75f5e3d4926ee92c9b95765e29208f1ae81c7130871a26914867d21d6829d719
sha512=13bfdf55f90e7201657188b84dcedafb1cc4b8f2f2a173fdb53cd98ddf427e43bb9e94aa105cd20576ebbd31fc680dd367835d78d06941e84a607793b2bc5086
Description
Validates exhaustive dependencies in useEffect, useLayoutEffect, useInsertionEffect, useCallback, useMemo and ensures hooks are called at the top level, never conditionally or inside loops.
Added to opam-repository:
README
react-rules-of-hooks-ppx
A ppx that validates React's Rules of Hooks at compile time.
- Exhaustive dependencies in
useEffect,useMemo,useCallback, etc. - Order of hooks: no conditional calls, top level only, and only inside component functions or custom hooks. Recognized component attributes:
[@react.component],[@react.client.component], and[@react.async.component].
Exhaustive dependencies
[@react.component]
let make = (~randomProp) => {
let (show, setShow) = React.useState(() => false);
React.useEffect1(
() => {
/* reads randomProp, but only [|show|] is declared */
if (randomProp) {
setShow(prevShow => !prevShow);
};
None;
},
[|show|],
);
<div />;
};15 | [|show|],
^^^^^^^^
Error (warning 22): exhaustive-deps: Missing 'randomProp' in the dependency array.
To suppress this warning, add [@disable_exhaustive_deps] before the expressionDependencies are tracked at member-path granularity, like eslint-plugin-react-hooks. input.page and input.limit are distinct dependencies. A declared input.page covers deeper uses such as input.page.size, but not input.limit and not the whole input; passing the whole record somewhere requires input itself. Duplicate detection, missing and unnecessary reports, and -corrections output all use the full member path.
Effects without a dependency array (React.useEffect(fn)) run after every render and can never observe stale values, so they get no exhaustiveness checking, same as eslint-plugin-react-hooks. A direct useState/useReducer setter call in such an effect warns about an infinite update chain, and unsuffixed useMemo/useCallback warn that the memoization does nothing.
Stable values are exempt from dependency arrays, with no configuration:
useState/useReducersetters anduseRefresults.- Same-file
use*wrappers around them, transitively:let useStateValue = initial => useReducer((_, next) => next, initial);makes setters returned byuseStateValueexempt in that file. - By naming convention, a second tuple element or record field named
set[A-Z]...,set_..., ordispatch...destructured from any hook call. This one is a heuristic; plain closures (let setLocal = ...), whole-return bindings (let setAll = Hook.use()), and other names are still checked.
Order of hooks
Calling hooks inside conditionals, loops, or nested functions fails the build:
[@react.component]
let make = (~condition) => {
if (condition) {
let (state, _) = React.useState(() => 0); /* That's wrong */
();
};
<div />;
};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(preprocess (pps reason-react react-rules-of-hooks-ppx))Suppress locally
Attach the attribute to the hook call:
[@disable_exhaustive_deps]
React.useEffect1(() => {...}, [|dep|]);
if (condition) {
[@disable_order_of_hooks]
useMyHook();
};In OCaml syntax the attribute goes after the expression: (useMyHook ())[@disable_order_of_hooks].
[@disable_order_of_hooks] remains the escape hatch for genuinely conditional hooks.
Flags
Append to the pps line:
-disable-exhaustive-depsturns off dependency checking.-disable-order-of-hooksturns off order-of-hooks checking.-correctionswrites.ppx-correctedfiles with suggested fixes for missing dependencies; review and accept them withdune promote:- React.useEffect0(() => { + React.useEffect1(() => { Js.log(dep1); None; - }); + }, [| dep1 |]);
Issues
Report any unexpected behaviour in the bug tracker
Acknowledgements
Thanks to @jchavarri
Dev Dependencies (8)
-
odoc
with-doc -
ocamlmerlin-mlx
with-dev-setup -
ocamlformat-mlx
with-dev-setup -
mlx
with-test | with-dev-setup -
ocaml-lsp-server
with-dev-setup -
ocamlformat
with-dev-setup -
melange
with-dev-setup -
reason
with-test & >= "3.10.0"
Used by
None
Conflicts
None