Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use 'union trick' to model hook dep arrays #574

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `React.Dep` is used to wrap each dependency in the array in a type-safe way.

Accordingly, for example, the two javascript calls:

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/use-state-use-effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions docs/usedebounce-custom-hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions docs/useeffect-hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
}, [||]);
}
```

Expand All @@ -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|]);
}
```
5 changes: 2 additions & 3 deletions docs/usereducer-hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
<div> {React.string(string_of_int(state.count))} </div>;
Expand Down
Loading