Connecting react components with xstate state machine library.
react-xstate
gives you easy access to xstate in the react world! ;)
Xstate allows you to improve state handling of your components by applying formal definition of a state machine including states and transitions. This allows you to better separate business logic from state handling and separate them into different files. You can use react-xstate
to transition your UI-programming to model-driven-development which besides better code structure removes major error source and enables visual documentation.
This library bases on the xstate by David Khourshid
- 📖 Read the documentation!
- Get inspired by 📽 the slides (🎥 video)
- Statecharts - A Visual Formalism for Complex Systems by David Harel
- Checkout xstate visualizer for graph generation by David Khourshid
The xstate
library implements the formal processing of state machines and leaves handling transitions, updating state and reducing actions to the user. This is where react-xstate
comes into play and integrates state and transition handling directly into your react components, only by applying a state machine
and action reducers
and returning a xstate
prop and a transition function. It also implements transition queueing to be able to fire transitions within action reducers.
npm install react-xstate --save
import { mountXstate } from 'react-xstate'
Mount the xstate machine to your component by applying a machine
definition and at least one actionReducer
to your component.
mountXstate(appMachine, [appReducer])(App)
This simple state machine implements an easy to use statechart that transitions between ping and pong and when you click in state ping you will trigger the consoleLog
action.
const appMachine = {
initial: 'pending',
states: {
ping: {
on: {
CLICK: {
pong: { actions: ['consoleLog'] }
}
}
},
pong: {
on: {
CLICK: 'ping',
}
},
}
}
const appReducer = (action) => {
if(action === 'consoleLog') {
console.log('Fired action: consoleLog')
}
}
class App extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit() {
// See transition definition prop below
this.props.transition({ type: 'CLICK' })
}
render() {
const { xstate: { value: state } } = this.props
console.log(`State: ${JSON.stringify(state)}`)
return (
<button onClick={this.handleSubmit}>Click</button>
);
}
}
export default mountXstate(appMachine, [appReducer])(App)
The withXstate
higher-order component takes a statechart definition (see xstate), an array of actionReducers and a component.
It adds and exposes two new props to your component: transition
and xstate
.
ActionReducers
are functions that are mounted onto the state machine and called upon every action execution. Return should be an object that is passed through as additional state onto the xstate prop.
Arg | Type | Description |
---|---|---|
action | string | Returns the current action called. |
event | object | Additional payload of the transition triggering the action. |
xstate | object | Access the xstate component itself to e.g. call transition from action. |
const reducer = (action, event, xstate) => {
const { transition } = xstate
if(action === 'loadData') {
fetch(event.url, event.payload)
return { loading: true }
}
}
This function is hooked onto your components props and fires events towards your state machine. Expects an object with the event type
and optionally and additional action payload that can be used by actionReducers to update the state.
handleClick = () => {
this.props.transition({ type: 'FETCH', url: 'http://github.com' })
}
To enable transition chains by calling transition in action reducers we included a queued transition handling that queues transitions while there is already one transition happening.
This object exposes the state of the state machine, including action reduced state, to enable the user to build stateful component logic.
render = () => {
const { xstate: { value: state } } = this.props
return (
<div>
<button>Send</button>
{state === 'loading' && <div>Loading...</div>}
</div>
);
}