Skip to content

Commit d951b9c

Browse files
authored
Merge pull request #1 from emeks-studio/poc
PoC: Restate
2 parents 7a7de80 + 3d6345e commit d951b9c

20 files changed

+2760
-1913
lines changed

HISTORY.md

Lines changed: 3 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,5 @@
1-
## 5.0.2
2-
3-
Changes:
4-
5-
- Add compat with React 18 (a1bd63a)
6-
7-
## 5.0.1
8-
9-
Changes:
10-
11-
- Add compat with ReScript React 0.11.x (6c0d979)
12-
13-
## 5.0.0
14-
15-
Fixes:
16-
17-
- Fixed effect cleanup (1816f51)
18-
19-
## 4.0.0
20-
21-
Changes:
22-
23-
- **Breaking change**: the argument order has been changed to match React's APIs (af9447e)
24-
25-
## 3.0.2
26-
27-
Changes:
28-
29-
- Update React & React DOM peer dependencies (44c6959)
30-
31-
## 3.0.1
32-
33-
Fixes:
34-
35-
- Fixed `bsconfig` name (thanks @jasim) (2ebd081)
36-
37-
## 3.0.0
38-
39-
Changes:
40-
41-
- Move to ReScript (62665af)
42-
- Move from reason-react to @rescript/react (62665af)
43-
- Rename project to `rescript-react-update` (62665af)
44-
45-
## 2.0.0
46-
47-
Changes:
48-
49-
- Make effect management preact-safe (76664b6)
50-
511
## 1.0.0
522

53-
Features:
54-
55-
- Add `ReactUpdateLegacy` for uncancellable effects, easing the migration process if copy pasting from record API reducers (c4ccec5)
56-
- Add `useReducerWithMapState` API to enable lazy state init (19eee9a)
57-
58-
## 0.1.1
59-
60-
Changes:
61-
62-
- Stop exposing fullState
63-
64-
## 0.1.0
65-
66-
Initial release
3+
* Fork from `rescript-react-update`.
4+
* Re-design and extend the API.
5+
* Fix Effect cleanup before re-run an effect function.

OLD_HISTORY.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Old History from original lib: `rescript-react-update`
2+
3+
## 5.0.2
4+
5+
Changes:
6+
7+
- Add compat with React 18 (a1bd63a)
8+
9+
## 5.0.1
10+
11+
Changes:
12+
13+
- Add compat with ReScript React 0.11.x (6c0d979)
14+
15+
## 5.0.0
16+
17+
Fixes:
18+
19+
- Fixed effect cleanup (1816f51)
20+
21+
## 4.0.0
22+
23+
Changes:
24+
25+
- **Breaking change**: the argument order has been changed to match React's APIs (af9447e)
26+
27+
## 3.0.2
28+
29+
Changes:
30+
31+
- Update React & React DOM peer dependencies (44c6959)
32+
33+
## 3.0.1
34+
35+
Fixes:
36+
37+
- Fixed `bsconfig` name (thanks @jasim) (2ebd081)
38+
39+
## 3.0.0
40+
41+
Changes:
42+
43+
- Move to ReScript (62665af)
44+
- Move from reason-react to @rescript/react (62665af)
45+
- Rename project to `rescript-react-update` (62665af)
46+
47+
## 2.0.0
48+
49+
Changes:
50+
51+
- Make effect management preact-safe (76664b6)
52+
53+
## 1.0.0
54+
55+
Features:
56+
57+
- Add `ReactUpdateLegacy` for uncancellable effects, easing the migration process if copy pasting from record API reducers (c4ccec5)
58+
- Add `useReducerWithMapState` API to enable lazy state init (19eee9a)
59+
60+
## 0.1.1
61+
62+
Changes:
63+
64+
- Stop exposing fullState
65+
66+
## 0.1.0
67+
68+
Initial release

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`.

bsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "rescript-react-update",
2+
"name": "rescript-react-restate",
33
"reason": {
44
"react-jsx": 3
55
},
@@ -24,6 +24,6 @@
2424
"number": "-30"
2525
},
2626
"suffix": ".bs.js",
27-
"bs-dependencies": ["@rescript/react"],
27+
"bs-dependencies": ["@rescript/react", "rescript-react-update"],
2828
"refmt": 3
2929
}

0 commit comments

Comments
 (0)