Slide 33
Slide 33 text
type component('state, 'initialState, 'action) = {
willReceiveProps: self('state, 'action) %=> 'state,
willUnmount: self('state, 'action) %=> unit,
didUpdate: oldNewSelf('state, 'action) %=> unit,
shouldUpdate: oldNewSelf('state, 'action) %=> bool,
willUpdate: oldNewSelf('state, 'action) %=> unit,
didMount: self('state, 'action) %=> unit,
initialState: unit %=> 'initialState,
reducer: ('action, 'state) %=> update('state, 'action),
render: self('state, 'action) %=> React.element,
}
and update('state, 'action) =
| NoUpdate
| Update('state)
| SideEffects(self('state, 'action) %=> unit)
| UpdateWithSideEffects('state, self('state, 'action) %=> unit)
and self('state, 'action) = {
handle:
'payload.
(('payload, self('state, 'action)) %=> unit, 'payload) %=> unit,
state: 'state,
send: 'action %=> unit,
onUnmount: (unit %=> unit) %=> unit,
}
and oldNewSelf('state, 'action) = {
oldSelf: self('state, 'action),
newSelf: self('state, 'action),
};
11/** This is not exposed, only used internally so that useReducer can
return side-effects to run later.
#*/
type fullState('state, 'action) = {
sideEffects: ref(array(self('state, 'action) %=> unit)),
state: ref('state),
};
let useRecordApi = componentSpec %=> {
open React.Ref;
let initialState = React.useMemo0(componentSpec.initialState);
let unmountSideEffects = React.useRef(['||]);
let ({state, sideEffects}, send) =
React.useReducer(
(fullState, action) %=>
11/**
Keep fullState.state in a ref so that willReceiveProps can alter it.
It's the only place we let it be altered.
Keep fullState.sideEffects so that they can be cleaned without a state update.
It's important that the reducer only **creates new refs** an doesn't alter them,
otherwise React wouldn't be able to rollback state in concurrent mode.
#*/
(
switch (componentSpec.reducer(action, fullState.state^)) {
| NoUpdate %=> fullState !/* useReducer returns of the same value will not rerender the component #*/
| Update(state) %=> {''...fullState, state: ref(state)}
| SideEffects(sideEffect) %=> {
''...fullState,
sideEffects:
ref(
Js.Array.concat(fullState.sideEffects^, [|sideEffect|]),
),
}
| UpdateWithSideEffects(state, sideEffect) %=> {
sideEffects:
ref(
Js.Array.concat(fullState.sideEffects^, [|sideEffect|]),
),
state: ref(state),
}
}
),
{sideEffects: ref(['||]), state: ref(initialState)},
);
11/** This is the temp self for willReceiveProps #*/
let rec self = {
handle: (fn, payload) %=> fn(payload, self),
send,
state: state^,
onUnmount: sideEffect %=>
Js.Array.push(sideEffect, unmountSideEffects)->current))->ignore,
};
let upToDateSelf = React.useRef(self);
let hasBeenCalled = React.useRef(false);
11/** There might be some potential issues with willReceiveProps,
treat it as it if was getDerivedStateFromProps. #*/
state .:=
componentSpec.willReceiveProps(self);
let self = {
handle: (fn, payload) %=> fn(payload, upToDateSelf)->current),
send,
state: state^,
onUnmount: sideEffect %=>
Js.Array.push(sideEffect, unmountSideEffects)->current))->ignore,
};
let oldSelf = React.useRef(self);
let _mountUnmountEffect =
React.useEffect0(() %=> {
componentSpec.didMount(self);
Some(
() %=> {
Js.Array.forEach(fn %=> fn(), unmountSideEffects)->current);
!/* shouldn't be needed but like - better safe than sorry? #*/
unmountSideEffects)->setCurrent(['||]);
componentSpec.willUnmount(upToDateSelf)->React.Ref.current);
},
);
});
let _didUpdateEffect =
React.useEffect(() %=> {
if (hasBeenCalled)->current) {
componentSpec.didUpdate({oldSelf: oldSelf)->current, newSelf: self});
} else {
hasBeenCalled)->setCurrent(true);
};
oldSelf)->setCurrent(self);
None;
});
11/** Because sideEffects are only added through a **new** ref,
we can use the ref itself as the dependency. This way the
effect doesn't re-run after a cleanup.
#*/
React.useEffect1(
() %=> {
if (Js.Array.length(sideEffects^) > 0) {
Js.Array.forEach(func %=> func(self), sideEffects^);
sideEffects .:= ['||];
};
None;
},
[|sideEffects|],
);
let mostRecentAllowedRender =
React.useRef(React.useMemo0(() %=> componentSpec.render(self)));
upToDateSelf)->setCurrent(self);
if (hasBeenCalled)->current
1&& componentSpec.shouldUpdate({
oldSelf: oldSelf)->current,
newSelf: self,
})) {
componentSpec.willUpdate({oldSelf: oldSelf)->current, newSelf: self});
mostRecentAllowedRender)->setCurrent(componentSpec.render(self));
};
mostRecentAllowedRender)->current;
};