diff --git a/docs/components.md b/docs/components.md
index 63350db34..132efca24 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -69,7 +69,7 @@ The component above could be called like this:
The next thing you might notice looking at this example is the use of hooks (`useState`). ReasonReact binds to [all of the hooks that React provides](https://reactjs.org/docs/hooks-intro.html) with only minor API differences. Please refer to their excellent documentation for more information on how hooks work and for best practices.
-The differences that you'll notice are mostly around listing dependencies. In React they are passed as an array, however, as Reason does not allow elements of different types in an array, a tuple of varying length is needed as final argument to the hook. For a tuple of length `N` you would need to call `useEffectN`, as otherwise the argument would not type match given the function's type signature which would in turn require a tuple of length `N`.
+The differences that you'll notice are mostly around listing dependencies. In React they are passed as a heterogeneous array, however, as Reason does not allow elements of different types in an array, a special wrapper function `React.dep` is used to type-cast each dependency in the array in a type-safe way. (It's type-safe because it's cast into an abstract type `dep` which does not allow any further operations.)
Accordingly, for example, the two javascript calls:
@@ -78,23 +78,15 @@ useEffect(effect, [dep1, dep2])
useEffect(effect, [])
```
-would be expressed as the following two reason calls:
+would be expressed as the following two Reason calls:
```reason
-useEffect2(effect, (dep1, dep2))
- /* ^^^ -- Note the number matching the dependencies' length */
-useEffect0(effect)
- /* ^^^ --- Compiles to javascript as `useEffect(effect, [])` */
+useEffectN(effect, [|dep1->dep, dep2->dep|]) // Note the type-safe wrapping
+// ^ compiles to JavaScript `useEffect(effect, [dep1, dep2])
+useEffectN(effect, [||])
+// ^ compiles to JavaScript `useEffect(effect, [])
```
-A notable exception is that when there is only one dependency, the relevant `useEffect1` call takes an array as the final argument to the hook. While tuples are compiled into JS arrays, the singleton would not be. Therefore, it is necessary to explicitly pass the dependency wrapped in an array.
-
-```reason
-useEffect1(effect, [|dep|])
-```
-
-However, as the length of the array is not specified, you could pass an array of arbitrary length, including the empty array, `[||]`.
-
Reason also always opts for the safest form of a given hook as well. So `React.useState` in JS can take an initial value or a function that returns an initial value. The former cannot be used safely in all situations, so ReasonReact only supports the second form which takes a function and uses the return.
## Hand-writing components
diff --git a/docs/use-state-use-effect.md b/docs/use-state-use-effect.md
index 4fc9d9fd5..b9bf7803d 100644
--- a/docs/use-state-use-effect.md
+++ b/docs/use-state-use-effect.md
@@ -12,12 +12,12 @@ let make = (~label, ~onSubmit) => {
let onCancel = _evt => setEditing(_ => false);
let onFocus = event => ReactEvent.Focus.target(event)##select();
- React.useEffect1(
+ React.useEffectN(
() => {
onChange(_ => label);
None
},
- [|label|],
+ [|label->React.dep|],
);
if (editing) {
diff --git a/docs/usedebounce-custom-hook.md b/docs/usedebounce-custom-hook.md
index 7e4cc13fd..2e6a02c17 100644
--- a/docs/usedebounce-custom-hook.md
+++ b/docs/usedebounce-custom-hook.md
@@ -7,14 +7,14 @@ title: A Custom useDebounce Hook
let useDebounce = (value, delay) => {
let (debouncedValue, setDebouncedValue) = React.useState(_ => value);
- React.useEffect1(
+ React.useEffectN(
() => {
let handler =
Js.Global.setTimeout(() => setDebouncedValue(_ => value), delay);
Some(() => Js.Global.clearTimeout(handler));
},
- [|value|],
+ [|value->React.dep|],
);
debouncedValue;
diff --git a/docs/useeffect-hook.md b/docs/useeffect-hook.md
index 395941246..ef6d5b810 100644
--- a/docs/useeffect-hook.md
+++ b/docs/useeffect-hook.md
@@ -11,11 +11,11 @@ Here's a simple example of how to use React's `useState` with `useEffects`.
```reason
[@react.component]
let make = () => {
- React.useEffect0(() => {
+ React.useEffectN(() => {
let id = subscription.subscribe();
/* clean up the subscription */
Some(() => subscription.unsubscribe(id));
- });
+ }, [||]);
}
```
@@ -26,10 +26,10 @@ With this, the subscription will only be recreated when `~source` changes
```reason
[@react.component]
let make = (~source) => {
- React.useEffect1(() => {
+ React.useEffectN(() => {
let id = subscription.subscribe();
/* clean up the subscription */
Some(() => subscription.unsubscribe(id));
- }, [|source|]);
+ }, [|source->React.dep|]);
}
```
diff --git a/docs/usereducer-hook.md b/docs/usereducer-hook.md
index 177e76f35..da88729f7 100644
--- a/docs/usereducer-hook.md
+++ b/docs/usereducer-hook.md
@@ -25,11 +25,10 @@ let make = () => {
{count: 0},
);
- /* useEffect hook takes 0 arguments hence, useEffect0 */
- React.useEffect0(() => {
+ React.useEffectN(() => {
let timerId = Js.Global.setInterval(() => dispatch(Tick), 1000);
Some(() => Js.Global.clearInterval(timerId));
- });
+ }, [||]);
/* ints need to be converted to strings, that are then consumed by React.string */
{React.string(string_of_int(state.count))}
;
diff --git a/src/React.re b/src/React.re
index 312bf78de..fa11b1def 100644
--- a/src/React.re
+++ b/src/React.re
@@ -186,41 +186,60 @@ external useReducerWithMapState:
('state, 'action => unit) =
"useReducer";
+/** A hook dependency. */
+type dep;
+
+/** [dep(value)] safely type-casts any [value] into a React hook dependency. */
+external dep: _ => dep = "%identity";
+
[@bs.module "react"]
external useEffect: ([@bs.uncurry] (unit => option(unit => unit))) => unit =
"useEffect";
+
[@bs.module "react"]
+external useEffectN:
+ ([@bs.uncurry] (unit => option(unit => unit)), array(dep)) => unit =
+ "useEffect";
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect0:
([@bs.uncurry] (unit => option(unit => unit)), [@bs.as {json|[]|json}] _) =>
unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect1:
([@bs.uncurry] (unit => option(unit => unit)), array('a)) => unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect2:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b)) => unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect3:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c)) => unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect4:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd)) => unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect5:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e)) =>
unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect6:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e, 'f)) =>
unit =
"useEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useEffectN"] [@bs.module "react"]
external useEffect7:
(
[@bs.uncurry] (unit => option(unit => unit)),
@@ -233,38 +252,51 @@ external useEffect7:
external useLayoutEffect:
([@bs.uncurry] (unit => option(unit => unit))) => unit =
"useLayoutEffect";
+
[@bs.module "react"]
+external useLayoutEffectN:
+ ([@bs.uncurry] (unit => option(unit => unit)), array(dep)) => unit =
+ "useLayoutEffect";
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect0:
([@bs.uncurry] (unit => option(unit => unit)), [@bs.as {json|[]|json}] _) =>
unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect1:
([@bs.uncurry] (unit => option(unit => unit)), array('a)) => unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect2:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b)) => unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect3:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c)) => unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect4:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd)) => unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect5:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e)) =>
unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect6:
([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e, 'f)) =>
unit =
"useLayoutEffect";
-[@bs.module "react"]
+
+[@deprecated "Use useLayoutEffectN"] [@bs.module "react"]
external useLayoutEffect7:
(
[@bs.uncurry] (unit => option(unit => unit)),
@@ -275,31 +307,43 @@ external useLayoutEffect7:
[@bs.module "react"]
external useMemo: ([@bs.uncurry] (unit => 'any)) => 'any = "useMemo";
+
[@bs.module "react"]
+external useMemoN: ([@bs.uncurry] (unit => 'any), array(dep)) => 'any =
+ "useMemo";
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo0:
([@bs.uncurry] (unit => 'any), [@bs.as {json|[]|json}] _) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo1: ([@bs.uncurry] (unit => 'any), array('a)) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo2: ([@bs.uncurry] (unit => 'any), ('a, 'b)) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo3: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c)) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo4: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd)) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo5:
([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd, 'e)) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo6:
([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd, 'e, 'f)) => 'any =
"useMemo";
-[@bs.module "react"]
+
+[@deprecated "Use useMemoN"] [@bs.module "react"]
external useMemo7:
([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd, 'e, 'f, 'g)) => 'any =
"useMemo";
@@ -311,40 +355,54 @@ type callback('input, 'output) = 'input => 'output;
external useCallback:
([@bs.uncurry] ('input => 'output)) => callback('input, 'output) =
"useCallback";
+
[@bs.module "react"]
+external useCallbackN:
+ ([@bs.uncurry] ('input => 'output), array(dep)) =>
+ callback('input, 'output) =
+ "useCallback";
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback0:
([@bs.uncurry] ('input => 'output), [@bs.as {json|[]|json}] _) =>
callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback1:
([@bs.uncurry] ('input => 'output), array('a)) => callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback2:
([@bs.uncurry] ('input => 'output), ('a, 'b)) => callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback3:
([@bs.uncurry] ('input => 'output), ('a, 'b, 'c)) =>
callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback4:
([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd)) =>
callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback5:
([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd, 'e)) =>
callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback6:
([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd, 'e, 'f)) =>
callback('input, 'output) =
"useCallback";
-[@bs.module "react"]
+
+[@deprecated "Use useCallbackN"] [@bs.module "react"]
external useCallback7:
([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd, 'e, 'f, 'g)) =>
callback('input, 'output) =
@@ -356,6 +414,16 @@ external useContext: Context.t('any) => 'any = "useContext";
[@bs.module "react"] external useRef: 'value => ref('value) = "useRef";
[@bs.module "react"]
+external useImperativeHandleN:
+ (
+ Js.Nullable.t(ref('value)),
+ [@bs.uncurry] (unit => 'value),
+ array(dep)
+ ) =>
+ unit =
+ "useImperativeHandle";
+
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle0:
(
Js.Nullable.t(ref('value)),
@@ -365,19 +433,19 @@ external useImperativeHandle0:
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle1:
(Js.Nullable.t(ref('value)), [@bs.uncurry] (unit => 'value), array('a)) =>
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle2:
(Js.Nullable.t(ref('value)), [@bs.uncurry] (unit => 'value), ('a, 'b)) =>
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle3:
(
Js.Nullable.t(ref('value)),
@@ -387,7 +455,7 @@ external useImperativeHandle3:
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle4:
(
Js.Nullable.t(ref('value)),
@@ -397,7 +465,7 @@ external useImperativeHandle4:
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle5:
(
Js.Nullable.t(ref('value)),
@@ -407,7 +475,7 @@ external useImperativeHandle5:
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle6:
(
Js.Nullable.t(ref('value)),
@@ -417,7 +485,7 @@ external useImperativeHandle6:
unit =
"useImperativeHandle";
-[@bs.module "react"]
+[@deprecated "Use useImperativeHandleN"] [@bs.module "react"]
external useImperativeHandle7:
(
Js.Nullable.t(ref('value)),
diff --git a/src/ReasonReactRouter.re b/src/ReasonReactRouter.re
index 65253cb64..589a3d89c 100644
--- a/src/ReasonReactRouter.re
+++ b/src/ReasonReactRouter.re
@@ -176,20 +176,23 @@ let useUrl = (~serverUrl=?, ()) => {
}
);
- React.useEffect0(() => {
- let watcherId = watchUrl(url => setUrl(_ => url));
+ React.useEffectN(
+ () => {
+ let watcherId = watchUrl(url => setUrl(_ => url));
- /**
+ /**
* check for updates that may have occured between
* the initial state and the subscribe above
*/
- let newUrl = dangerouslyGetInitialUrl();
- if (urlNotEqual(newUrl, url)) {
- setUrl(_ => newUrl);
- };
-
- Some(() => unwatchUrl(watcherId));
- });
+ let newUrl = dangerouslyGetInitialUrl();
+ if (urlNotEqual(newUrl, url)) {
+ setUrl(_ => newUrl);
+ };
+
+ Some(() => unwatchUrl(watcherId));
+ },
+ [||],
+ );
url;
};
diff --git a/website/blog/2019-04-10-react-hooks.md b/website/blog/2019-04-10-react-hooks.md
index 56e98866b..f8f7f3357 100644
--- a/website/blog/2019-04-10-react-hooks.md
+++ b/website/blog/2019-04-10-react-hooks.md
@@ -37,10 +37,10 @@ let make = () => {
{count: 0}
);
- React.useEffect0(() => {
+ React.useEffectN(() => {
let timerId = Js.Global.setInterval(() => dispatch(Tick), 1000);
Some(() => Js.Global.clearInterval(timerId))
- });
+ }, [||]);
{ReasonReact.string(string_of_int(state.count))}
;
};