Skip to content

Instantly share code, notes, and snippets.

@lithin
Created May 30, 2019 15:33
Show Gist options
  • Save lithin/3ef36a005ae8eee528b29fd5fbb43522 to your computer and use it in GitHub Desktop.
Save lithin/3ef36a005ae8eee528b29fd5fbb43522 to your computer and use it in GitHub Desktop.
Example of react state machine with hooks
// @flow
import * as React from 'react';
import { Machine, interpret } from 'xstate';
import { Discovery } from '~/screens/install-devices/discovery';
import { GetStarted } from '~/screens/install-devices/get-started';
import { RenameDevice } from '~/screens/install-devices/rename-device';
import { mapping, type Props as ConnectorProps } from '~/core/domains/installation/devices/mappings';
import { hiveNavigate } from '~/navigation';
import * as routePaths from '~/constants/routes';
const states = {
DISCOVERY: 'DISCOVERY',
GET_STARTED: 'GET_STARTED',
RENAME: 'RENAME',
DONE: 'DONE',
};
const transitions = {
TO_DISCOVERY: 'TO_DISCOVERY',
TO_INSTALL: 'TO_INSTALL',
TO_RENAME: 'TO_RENAME',
TO_START: 'TO_START',
TO_DONE: 'TO_DONE',
};
const installDevicesMachin = Machine(
{
id: 'installDevices',
initial: states.GET_STARTED,
states: {
[states.GET_STARTED]: {
on: { [transitions.TO_DISCOVERY]: states.DISCOVERY, [transitions.TO_INSTALL]: states.DONE },
},
[states.DISCOVERY]: {
on: { [transitions.TO_RENAME]: states.RENAME, [transitions.TO_START]: states.GET_STARTED },
onEntry: ['loadDevices'],
onExit: ['stopAcquireDevices'],
},
[states.RENAME]: {
on: { [transitions.TO_DONE]: states.DONE },
onExit: ['finishLoadingDevices'],
},
[states.DONE]: {
type: 'final',
onEntry: ['navigate'],
},
},
},
{
actions: {
loadDevices: (context, event) => {
// loadInstallDevices is a redux action
event.loadInstallDevices();
},
navigate: (context, event) => {
// this is just navigation to different routes
if (event.type === transitions.TO_INSTALL) {
hiveNavigate.dismissModal();
}
if (event.type === transitions.TO_DONE) {
hiveNavigate.popToRoot(routePaths.install).then(() => hiveNavigate.dismissModal());
}
},
stopAcquireDevices: (context, event) => {
// stopAcquireDevices is a redux action
event.stopAcquireDevices();
},
finishLoadingDevices: (context, event) => {
// finishInstallDevices is a redux action; payload comes from a child component (from a form)
event.finishInstallDevices(event.payload);
},
},
}
);
export function useMachine(machine: Object, options?: Object) {
// Keep track of the current machine state
const [current, setCurrent] = React.useState(machine.initialState);
// Reference the service
const serviceRef = React.useRef(null);
// Create the service only once
// See https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily
if (serviceRef.current === null) {
serviceRef.current = interpret(machine, options).onTransition(state => {
// Update the current machine state when a transition occurs
if (state.changed) {
setCurrent(state);
}
});
}
const service = serviceRef.current;
React.useEffect(
() => {
// Start the service when the component mounts
service.start();
return () => {
// Stop the service when the component unmounts
service.stop();
};
},
[service]
);
return [current, service.send];
}
const Component = (props: ConnectorProps) => {
const { countryCode, devices, finishedAcquiring, loadInstallDevices, identifyDevice, isInstallInProgress } = props;
const [current, send] = useMachine(installDevicesMachin);
if (current.matches(states.DONE)) {
return null;
}
console.log(current.value);
return (
<React.Fragment>
{current.matches(states.GET_STARTED) ? (
<GetStarted
navigateToInstallDevices={() => send(transitions.TO_INSTALL, props)}
progressInstallStage={() => send(transitions.TO_DISCOVERY, props)}
countryCode={countryCode}
/>
) : null}
{current.matches(states.DISCOVERY) ? (
<Discovery
countryCode={countryCode}
devices={devices}
finishedAcquiring={finishedAcquiring}
loadInstallDevices={loadInstallDevices}
progressInstallStage={() => send(transitions.TO_RENAME, props)}
cancelDiscovery={() => send(transitions.TO_START, props)}
/>
) : null}
{current.matches(states.RENAME) ? (
<RenameDevice
countryCode={countryCode}
devices={devices}
identifyDevice={identifyDevice}
isSaving={isInstallInProgress}
finishInstallDevices={payload => send(transitions.TO_DONE, { ...props, payload })}
/>
) : null}
</React.Fragment>
);
};
/*
* mapping is a function that is pretty muhc equal to `connect(mapStateToProps, mapDisdpatchToProps)`
* it supplies us with updated data and gives us access to redux actions
*/
export const TestStateMachine = mapping(Component);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment