Skip to content

Commit 4216226

Browse files
Add Documentation
1 parent 0691532 commit 4216226

File tree

3 files changed

+75
-70
lines changed

3 files changed

+75
-70
lines changed

README.md

Lines changed: 73 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,103 @@
1-
# rescript-react-update
1+
# rescript-react-restate
22

3-
> useReducer with updates and side effects!
3+
This library is a fork and re-design of [rescript-react-update](https://github.com/bloodyowl/rescript-react-update).
4+
5+
Essentially what introduce is a fix on the effect cancellation mechanism. A fix in terms of following
6+
React's philosophy about how we should ensure effects cancellation before re-render.
7+
8+
As a consequence, of the fix, the library also introduce an elegant approach in the separation between pure state management and side effects. By introducing the concept of `deferred actions` and `schedulers`.
9+
10+
A `deferred action` is an `action` that is not immediately dispatched, but rather scheduled to be dispatched later.
11+
In contrast with `reducers` (that given an action and the state, provides the new state), a `scheduler` is a function that given the current context and a deferred action, can execute (user defined) side effects, and return (or not) a cleanup/cancellation function associated with.
412

513
## Installation
614

715
```console
8-
$ yarn add rescript-react-update
16+
$ yarn add rescript-react-restate
917
```
1018

1119
or
1220

1321
```console
14-
$ npm install --save rescript-react-update
22+
$ npm install --save rescript-react-restate
1523
```
1624

17-
Then add `rescript-react-update` to your `bsconfig.json` `bs-dependencies` field.
25+
Then add `rescript-react-restate` to your `bsconfig.json` `bs-dependencies` field.
1826

19-
## ReactUpdate.useReducer
27+
## Handling side effects (Asynchronous actions, logging, etc.)
2028

21-
```reason
22-
type state = int;
23-
24-
type action =
25-
| Increment
26-
| Decrement;
27-
28-
[@react.component]
29-
let make = () => {
30-
let (state, send) =
31-
ReactUpdate.useReducer((state, action) =>
32-
switch (action) {
33-
| Increment => Update(state + 1)
34-
| Decrement => Update(state - 1)
35-
},
36-
0
37-
);
38-
<div>
39-
{state->React.int}
40-
<button onClick={_ => send(Decrement)}> {"-"->React.string} </button>
41-
<button onClick={_ => send(Increment)}> {"+"->React.string} </button>
42-
</div>;
43-
};
44-
```
29+
`Restate` powers up reducers by allow them not just update state, but also defer an action to be dispatched later if is desired. This is useful when you want to handle side effects (like logging, network requests, etc.) after the state has been updated.
4530

46-
### Lazy initialisation
31+
```reason
4732
48-
## ReactUpdate.useReducerWithMapState
33+
// Your state
4934
50-
If you'd rather initialize state lazily (if there's some computation you don't want executed at every render for instance), use `useReducerWithMapState` where the first argument is a function taking `unit` and returning the initial state.
35+
type state = int
5136
52-
```reason
53-
type state = int;
37+
// Your actions (both immediate and deferred ones)
5438
5539
type action =
5640
| Increment
57-
| Decrement;
58-
59-
[@react.component]
41+
| Decrement
42+
43+
type deferredAction =
44+
| LogIncrement
45+
| LogDecrement
46+
47+
// Because of implementation details related on how we clean up the side effects, we need to provide a way to identify each the deferred action. In other words, deferred actions must have a unique identifier.
48+
module DeferredAction: Restate.HasDeferredAction with type t = deferredAction = {
49+
type t = deferredAction
50+
let variantId = action =>
51+
switch action {
52+
| LogIncrement => "LogIncrement"
53+
| LogDecrement => "LogDecrement"
54+
}
55+
}
56+
57+
// Instantiate the reducer with your deferred action type
58+
module RestateReducer = Restate.MakeReducer(DeferredAction)
59+
60+
// A Reducer now can update the state and schedule deferred actions (if they need to)
61+
let reducer = (state, action) =>
62+
switch action {
63+
| Increment =>
64+
RestateReducer.UpdateWithDeferred(
65+
state + 1,
66+
LogIncrement,
67+
)
68+
| Decrement =>
69+
RestateReducer.UpdateWithDeferred(
70+
state - 1,
71+
LogDecrement,
72+
)
73+
}
74+
75+
// A Scheduler handle deferred actions by triggering side effects and returning a cleanup function (if necessary)
76+
let scheduler: (RestateReducer.self<state, action>, deferredAction) => option<unit=>unit> =
77+
(self, deferredAction) =>
78+
switch deferredAction {
79+
| LogIncrement =>
80+
Js.log2("increment side effect: ", self.state)
81+
// Note: The state on the cleanup will the content of this scope, and
82+
// not the previous one that exist at moment of running the function.
83+
Some(() => Js.log2("increment cleanup: ", self.state))
84+
| LogDecrement =>
85+
Js.log2("decrement side effect: ", self.state)
86+
Some(() => Js.log2("decrement cleanup: ", self.state))
87+
}
88+
89+
@react.component
6090
let make = () => {
61-
let (state, send) =
62-
ReactUpdate.useReducerWithMapState(
63-
(state, action) =>
64-
switch (action) {
65-
| Increment => Update(state + 1)
66-
| Decrement => Update(state + 1)
67-
},
68-
() => 0
69-
);
91+
let (state, send, _defer) = RestateReducer.useReducer(reducer, scheduler, 0)
7092
<div>
7193
{state->React.int}
7294
<button onClick={_ => send(Decrement)}> {"-"->React.string} </button>
7395
<button onClick={_ => send(Increment)}> {"+"->React.string} </button>
74-
</div>;
75-
};
96+
</div>
97+
}
7698
```
7799

78-
### Cancelling a side effect
79-
80-
The callback you pass to `SideEffects` & `UpdateWithSideEffect` returns an `option(unit => unit)`, which is the cancellation function.
100+
## Lazy initialisation
81101

82-
```reason
83-
// doesn't cancel
84-
SideEffects(({send}) => {
85-
Js.log(1);
86-
None
87-
});
88-
// cancels
89-
SideEffects(({send}) => {
90-
let request = Request.make();
91-
request->Future.get(payload => send(Receive(payload)))
92-
Some(() => {
93-
Request.cancel(request)
94-
})
95-
});
96-
```
102+
If you'd rather initialize state lazily (if there's some computation you don't want executed at every render for instance), use `useReducerWithMapState` where the first argument is a function taking `unit` and returning the initial state.
97103

98-
If you want to copy/paste old reducers that don't support cancellation, you can use `ReactUpdateLegacy` instead in place of `ReactUpdate`. Its `SideEffects` and `UpdateWithSideEffects` functions accept functions that return `unit`.

examples/Counter_SideEffects.res

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ module ReactRestate = {
7777
switch deferredAction {
7878
| LogIncrement =>
7979
Js.log2("increment side effect: ", self.state)
80-
// Note: the state on the cleanup will the content of this scope, and
80+
// Note: The state on the cleanup will the content of this scope, and
8181
// not the previous one that exist at moment of running the function.
8282
Some(() => Js.log2("increment cleanup: ", self.state))
8383
| LogDecrement =>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "rescript-react-update",
2+
"name": "rescript-react-restate",
33
"version": "1.0.0",
44
"scripts": {
55
"dev": "yarn re:build && parcel examples/index.html",

0 commit comments

Comments
 (0)