Skip to content

Instantly share code, notes, and snippets.

@EliaECoyote
Created November 27, 2019 08:34
Show Gist options
  • Save EliaECoyote/7acb3b7c5738e1f503d97fd4ddd95a7f to your computer and use it in GitHub Desktop.
Save EliaECoyote/7acb3b7c5738e1f503d97fd4ddd95a7f to your computer and use it in GitHub Desktop.
ReasonML useMachine react hook
/**
* 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