An declarative state machine to react.
npm i --save react-against-the-machine
The project needs this nodejs and npm version restrictions:
"engines": {
"node": ">=16.9.1 <16.8.0",
"npm": "~7.24.0"
}
This project has these peer dependencies:
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
In order to install this peer dependencies, you need an npm version up to 7.x.
This peer dependencies are installed by the npm install
command.
You could build this react state machine with:
npm run build
and take the bundle into dist
folder
You could run tests with:
npm run test
Run test with watch mode:
npm run test:watch
Run test generating coverage report:
npm run test:coverage
lint your code with:
npm run lint
Fix your linted errors with:
npm run lint:fix
Format your code syntax with:
npm run format
The State machine has a <MachineProvider>
context API that storage all components to the Machine State and handle the state management.
The useMachine
hook is used to access the state machine in Machine context API.
import { useMachine } from 'react-against-the-machine';
const machine = useMachine();
We have some pieces as react components to represent the states and transitions of the state machine.
- Machine
- State
- Content
- Transition
<MachineProvider>
<Machine>
<State>
<Content>
<ComponentExample />
</Content>
<Transition />
</State>
</Machine>
</MachineProvider>
A react component that represents the state machine wrapper.
import Machine, { MachineProvider } from 'react-against-the-machine';
import { laGuaGua as bus } from 'laguagua';
<MachineProvider>
<Machine initial="componentA" bus={bus} logged={false}>
<!-- here should be the state machine States -->
</Machine>
</MachineProvider>
Machine needs some props:
- initial
string
- the initial state id of the machine. - bus
object
- the bus object of the state machine to publish/subscribe events that implement the IMachineBus interface. - logged
boolean
- the user logged status. This will be used to transition or not to some states depending on their private/public status.
We are using to our example the bus Laguagua, but you can use any other bus event that implements the IBus interface.
export type MachineBusHandler = (message: string, data?: Object) => void;
export interface IMachineBus {
publish: (message: string, data?: Object) => void;
subscribe: (message: string, trigger: BusHandler) => void;
clear: () => void;
}
A react component that represents a state of the state machine.
- Any state has a unique id.
- Any state could have transitions to other states.
- Any state should have a content react component to render.
import Machine, { MachineProvider, State } from 'react-against-the-machine';
// import { laGuaGua as bus } from 'laguagua';
{
/*
<MachineProvider>
<Machine initial="componentA" bus={bus} logged={false}>
*/
}
<State id="componentA" private={false}>
{/* here should be the state transition and the state content */}
</State>;
{
/*
</Machine>
</MachineProvider>
*/
}
State needs some props:
- id
string
- the state id to this state. - private
boolean
(default true) - if is private, the state only render the content if user is logged. - params
string[]
- params that could be read in url when machine state transition to this state and come back as a param whenonEnter
callback will be executed in the State lifecycle. - onEnter
(params?: Map<string, string>) => void
- is execute when the new State is mounted, come back params specified to be read in URL like a key/value map.
import Machine, { MachineProvider, State } from 'react-against-the-machine';
// import { laGuaGua as bus } from 'laguagua';
const handlerEnterOnStateA = (params?: Map<string, string>): void => {
console.log(`enter on sign up wit ${params?.get('code')} params`);
};
{
/*
<MachineProvider>
<Machine initial="componentA" bus={bus} logged={false}>
*/
}
<State id="A" private={false} params={['code']} onEnter={handlerEnterOnStateA}>
{/* here should be the state transition and the state content */}
</State>;
{
/*
</Machine>
</MachineProvider>
*/
}
Then if user or state machine try to access to {HOST}/A?code="123"
, we could get the code value when onEnter state callback was executed.
A react component that represents a transition to other state
- Transition should be placed inside the
<State />
component that wants to go to another state.
import { Transition } from 'react-against-the-machine';
<Transition event="go::componentA" state="componentA" />;
Transition needs some props:
- event
string
- the event to trigger this transition. - state
string
- the state id to go to.
A react component that render a react component that be wrapper by when machine is in this state.
stateDiagram-v2
componentA --> componentB: go-to-componentB
componentB--> componentA: go-to-componentA
import React from 'react';
// import the react against the machine pieces
import Machine, { MachineProvider, State, Transition, Content } from 'react-against-the-machine';
// import any bus that implements the IBus interface
import { laGuaGua as bus } from 'laguagua';
import ComponentA from './componentA';
import ComponentB from './componentB';
const App = () => {
const onTransitionToComponentB = (): void => {
console.log('Hey we are in component B');
};
return (
<MachineProvider>
<Machine initial="componentA" bus={bus} logger={true}>
<State id="componentA" private={false}>
<Content>
<ComponentA />
</Content>
<Transition event="go:to:componentB" state="componentB" onEnter={onTransitionToComponentB} />
</State>
<State id="componentB" private={false}>
<Content>
<ComponentB />
</Content>
<Transition event="go:to:componentA" state="componentA" />
</State>
</Machine>
</MachineProvider>
);
};
export default App;
You could build and run the real example that we have here:
cd src/example-ratm
npm i
npm start
Put follow URL in your local browser to read code param in url
http://localhost:3000/State3?code="123"