Skip to content

Commit 9bd54f6

Browse files
Implement useReducerWithMapState
1 parent e5b0d16 commit 9bd54f6

File tree

2 files changed

+102
-35
lines changed

2 files changed

+102
-35
lines changed

examples/BasicUsage.res

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
2-
31
module ReactUpdate = {
42
type action = Tick | Reset
53
type state = {elapsed: int}
6-
74
@react.component
85
let make = () => {
96
let (state, send) = ReactUpdate.useReducerWithMapState(
@@ -14,6 +11,7 @@ module ReactUpdate = {
1411
{elapsed: state.elapsed + 1},
1512
({send}) => {
1613
let timeoutId = Js.Global.setTimeout(() => send(Tick), 1_000)
14+
Js.Console.log2("schedule next tick: ", timeoutId)
1715
Some(() => {
1816
Js.Console.log2("cleanup: ", timeoutId)
1917
Js.Global.clearTimeout(timeoutId)
@@ -38,31 +36,45 @@ module ReactUpdate = {
3836
module ReactRestate = {
3937
type action = Tick | Reset
4038
type state = {elapsed: int}
39+
type deferredAction = ScheduleNextTick
40+
module DeferredAction: Restate.HasDeferredAction with type t = deferredAction = {
41+
type t = deferredAction
42+
let variantId = action =>
43+
switch action {
44+
| ScheduleNextTick => "ScheduleNextTick"
45+
}
46+
}
47+
module RestateReducer = Restate.MakeReducer(DeferredAction)
48+
let reducer = (state, action) =>
49+
switch action {
50+
| Tick =>
51+
RestateReducer.UpdateWithDeferred(
52+
{elapsed: state.elapsed + 1},
53+
ScheduleNextTick
54+
)
55+
| Reset => RestateReducer.Update({elapsed: 0})
56+
}
57+
let scheduler: (RestateReducer.self<state, action>, deferredAction) => option<unit=>unit> =
58+
(self, action) =>
59+
switch action {
60+
| ScheduleNextTick =>
61+
let timeoutId = Js.Global.setTimeout(() => self.send(Tick), 1_000)
62+
Js.Console.log2("schedule next tick: ", timeoutId)
63+
Some(() => {
64+
Js.Console.log2("cleanup: ", timeoutId)
65+
Js.Global.clearTimeout(timeoutId)
66+
})
67+
}
4168
@react.component
4269
let make = () => {
43-
React.null
44-
// let (state, send) = Restate.useReducerWithMapState(
45-
// (state, action) =>
46-
// switch action {
47-
// | Tick =>
48-
// UpdateWithSideEffects(
49-
// {elapsed: state.elapsed + 1},
50-
// ({send}) => {
51-
// let timeoutId = Js.Global.setTimeout(() => send(Tick), 1_000)
52-
// Some(() => Js.Global.clearTimeout(timeoutId))
53-
// },
54-
// )
55-
// | Reset => Update({elapsed: 0})
56-
// },
57-
// () => {elapsed: 0},
58-
// )
59-
// React.useEffect0(() => {
60-
// send(Tick)
61-
// None
62-
// })
63-
// <div>
64-
// {state.elapsed->Js.String.make->React.string}
65-
// <button onClick={_ => send(Reset)}> {"Reset"->React.string} </button>
66-
// </div>
70+
let (state, send, _defer) = RestateReducer.useReducerWithMapState(reducer, scheduler, () => {elapsed: 0})
71+
React.useEffect0(() => {
72+
send(Tick)
73+
None
74+
})
75+
<div>
76+
{state.elapsed->Js.String.make->React.string}
77+
<button onClick={_ => send(Reset)}> {"Reset"->React.string} </button>
78+
</div>
6779
}
6880
}

src/Restate.res

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,67 @@ module MakeReducer = (DeferredAction: HasDeferredAction) => {
132132
// This way, we hide implementation (unsafe) tricks.
133133
(userState, send, defer)
134134
}
135-
}
136135

137-
// TODO: Implement this for Restate
138-
// ReactUpdate.useReducerWithMapState (reducer<'state, 'action>, () => 'state) => ('state, dispatch<'action>)
139-
// React.useReducerWithMapState(
140-
// ('state, 'action) => 'state,
141-
// 'initialState,
142-
// 'initialState => 'state,
143-
// ) => ('state, 'action => unit)
136+
let useReducerWithMapState = (
137+
reducer: reducer<'state, 'action>, // The reducer provided by the user
138+
scheduler: scheduler<'state, 'action>, // The scheduler provided by the user
139+
createInitialState: () => 'state, // A function to map the initial state
140+
) => {
141+
let cleanupFnsRef: React.ref<Belt.Map.String.t<option<unit => unit>>> = React.useRef(Belt.Map.String.empty)
142+
let ({userState, deferredActionsQueue}, internalDispatch) = React.useReducerWithMapState(
143+
({userState, deferredActionsQueue} as internalState, internalAction) =>
144+
switch internalAction {
145+
| WiredAction(action) =>
146+
switch reducer(userState, action) {
147+
| NoUpdate => internalState
148+
| Update(state) => {...internalState, userState: state}
149+
| UpdateWithDeferred(state, deferredAction) => {
150+
userState: state,
151+
deferredActionsQueue: Belt.List.concat(deferredActionsQueue, list{deferredAction}),
152+
}
153+
| Deferred(deferredAction) => {
154+
...internalState,
155+
deferredActionsQueue: Belt.List.concat(deferredActionsQueue, list{deferredAction}),
156+
}
157+
}
158+
| PushDeferred(deferredAction) => {
159+
...internalState,
160+
deferredActionsQueue: Belt.List.concat(deferredActionsQueue, list{deferredAction}),
161+
}
162+
| PopDeferred(tailDeferredActions) => {
163+
...internalState,
164+
deferredActionsQueue: tailDeferredActions,
165+
}
166+
}
167+
, (), initialState => {userState: createInitialState(initialState), deferredActionsQueue: list{}}
168+
)
169+
let defer: schedule = deferredAction => internalDispatch(PushDeferred(deferredAction))
170+
let send: dispatch<'action> = action => internalDispatch(WiredAction(action))
171+
React.useEffect1(() => {
172+
switch (deferredActionsQueue) {
173+
| list{deferredAction, ...queueTail} =>
174+
cleanupFnsRef.current
175+
->Belt.Map.String.get(DeferredAction.variantId(deferredAction))
176+
->Belt.Option.map(mPrevCleanupFn => mPrevCleanupFn->Belt.Option.map(prevCleanupFn => prevCleanupFn()))
177+
->ignore
178+
let mNewCleanupFn = scheduler({state: userState, send, defer}, deferredAction)
179+
cleanupFnsRef.current = cleanupFnsRef.current->Belt.Map.String.set(DeferredAction.variantId(deferredAction), mNewCleanupFn)
180+
internalDispatch(PopDeferred(queueTail))
181+
| list{} => ()
182+
}
183+
None
184+
}, [deferredActionsQueue])
185+
React.useEffect0(() => {
186+
Some(() => {
187+
cleanupFnsRef.current
188+
->Belt.Map.String.valuesToArray
189+
->Belt.Array.forEach(
190+
mCleanupFn => mCleanupFn->Belt.Option.forEach(cleanupFn => cleanupFn())
191+
)
192+
}
193+
)
194+
}
195+
)
196+
(userState, send, defer)
197+
}
198+
}

0 commit comments

Comments
 (0)