Created
November 27, 2019 08:34
-
-
Save EliaECoyote/7acb3b7c5738e1f503d97fd4ddd95a7f to your computer and use it in GitHub Desktop.
ReasonML useMachine react hook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* describes the outcome of the machine reducer | |
* fn. | |
* The Invalid variant value describes the case | |
* where an undesired event is received | |
* (cannot receive the event while being in | |
* current state). | |
* The Ignored variant value describes the case | |
* where we just want to not take action after | |
* receiving a specific event. | |
*/ | |
type outcome('state, 'reason) = | |
| Valid('state) | |
| Invalid('reason) | |
| Ignored; | |
type fsmReducer('state, 'event, 'reason) = | |
(~state: 'state, ~event: 'event) => outcome('state, 'reason); | |
/** | |
* uses the reducer parameter to return only | |
* actual machine state values, and not *outcomes*. | |
*/ | |
let getMextState = (reducer, state, event) => { | |
switch (reducer(~state, ~event)) { | |
| Valid(value) => value | |
| Ignored => state | |
| Invalid(reason) => | |
// the action taken when an invalid when | |
// encountering an invalid event request | |
// can be customized here | |
Js.log(reason); | |
state; | |
}; | |
}; | |
type t('state, 'event, 'reason) = | |
(~reducer: fsmReducer('state, 'event, 'reason), ~initialValue: 'state) => | |
('state, 'event => unit); | |
/** | |
* react hook that manages a FSM. | |
* There's no static chart here - the FSM logic is | |
* actually contained in the reducer function, | |
* by using the combination of tuple, variants | |
* and pattern matching reasonML features | |
*/ | |
let useMachine: t('state, 'event, 'reason) = | |
(~reducer, ~initialValue) => { | |
let enhancedReducerRef = React.useRef(None); | |
let getReducerWrapper = () => { | |
// creates the reducerWrapper with *reducerWrapperFactory*, | |
// but *only once*! | |
switch (enhancedReducerRef->React.Ref.current) { | |
| Some(value) => value | |
| None => | |
// uses partial application & currying to generate | |
// a reducer that returns actual machine state values | |
let enhancedReducer = getMextState(reducer); | |
enhancedReducerRef->React.Ref.setCurrent(Some(enhancedReducer)); | |
enhancedReducer; | |
}; | |
}; | |
React.useReducer(getReducerWrapper(), initialValue); | |
}; | |
/** | |
* ******************* | |
* ** Usage example ** | |
* ******************* | |
*/ | |
type status = | |
| Idle | |
| Fetching | |
| Error | |
| Success(string); | |
type events = | |
| LoadData | |
| LoadSuccess(string) | |
| LoadFailed; | |
let reducer = (~state, ~event) => | |
switch (state, event) { | |
| (_, LoadData) => Valid(Fetching) | |
| (Fetching, LoadSuccess(value)) => Success(value)->Valid | |
| (Fetching, LoadFailed) => Valid(Error) | |
| _ => Invalid(("invalid template machine event", state, event)) | |
}; | |
[@react.component] | |
let make = () => { | |
let (state, sendEvent) = useMachine(~reducer, ~initialValue=Idle); | |
(); | |
// .... | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment