A declarative state container built on top of xState
xState-store is a library built on top of xstate, that allows you to access your machines/services through your React application and simplify sharing the same instance/actor between your different components.
Why ?
Because xState and state machines are an awesome way to simplify complex applications logic especially for frontend applications using React library.
Sometimes we need to have a global store or make different components sharing the same logic coming form the same instance, it's where xState-store can helps you.
Installation
Using npm:
$ npm install xstate-store xstate @xstate/react
Usage
xstate-store exposes multiple functions that will help you managing your global application states.
createInstance
To create a new machine instance, you could use createInstance
method that uses the createMachine
and interpret
functions from xstate behind the scenes.
createInstance(
instanceId: string // it's the unique id that will define the instance and helps us accessing it through the application
config: MachineDefinition // it's the machine definition that we pass to `createMachine` in xstate
options?: MachineOptions // optional: the options that would be passed to the xstate `createMachine` function
)
For example, if we want to create an instance with the id LIGHT_INSTANCE_ID
, we can do something like this:
const LIGHT_INSTANCE_ID = '_LIGHT_INSTANCE_ID_'
createInstance(LIGHT_INSTANCE_ID, {
id: 'test-machine',
initial: 'off',
context: {
usageNumber: 0,
},
states: {
off: {
on: {
TOGGLE: {
target: 'on',
actions: assign({
usageNumber: (context) => context.usageNumber + 1,
})
}
}
},
on: {
on: {
TOGGLE: 'off'
}
},
}
})
stopInstance
Like xstate does, you can stop the instance of your service using the stopInstance
function by passing the instanceId
.
stopInstance(LIGHT_INSTANCE_ID)
sendToInstance
sendToInstance
function allows you to send
events to your service using the same send
function from xState.
For example, if we want to send an event TOGGLE
to our previous service, we can do something like this:
sendToInstance(
LIGHT_INSTANCE_ID,
{ type: 'TOGGLE' }
)
useInstance
useInstance
hook works same as the useActor
function from xstate, you can pass directly your instanceId
to the hook in order to retrieve the current state
of the service and a send
function.
function MyComponent() {
const [state, send] = useInstance(LIGHT_INSTANCE_ID)
return (
<p>The light is now: <strong>{state.value}</strong></p>
);
}
useDataFromContext
useDataFromContext
hook allow us more easily accessing the context data through the application.
The purpose is that you can retrieve an object with defined values from the context directly.
The hook will return an object with all asked data passed as an array or string to the hook.
For example, in our previous instance we have a value usageNumber
in our context. we can retrieve and use it like this:
export default function Component2() {
const data = useDataFromContext(LIGHT_INSTANCE_ID, 'usageNumber')
// we can also pass an array to retrieve multiple values directly
// const data = useDataFromContext(LIGHT_INSTANCE_ID, ['usageNumber', 'otherData'])
return (
<p>
Number of light usage: <strong>{data.usageNumber}</strong>
</p>
)
}
Not passing a second argument to useDataFromContext
or passing an empty array will return the whole context to you so you can use it directly like the example below:
export default function Component2() {
const context = useDataFromContext(LIGHT_INSTANCE_ID, [])
// same as doing like this:
// const data = useDataFromContext(LIGHT_INSTANCE_ID)
return (
<p>
Number of light usage: <strong>{context.usageNumber}</strong>
</p>
)
}
Making actors communicating
One of the challenges we are all facing using xState is how to make multiple actors communicating between them if they don't have any hierarchical relationship. xstate-store
helps you facing this challenge using the receptionnist pattern.
For example, you can use the sendToInstance
function as an action from another service in order to send an event to another actor using its id (reference).
The solution is inspired from the RFC that the xState team is working on.
const FIRST_LIGHT_INSTANCE_ID = '_FIRST_LIGHT_INSTANCE_ID_'
createInstance(LIGHT_INSTANCE_ID, {
// machine definition here
})
const SECOND_LIGHT_INSTANCE_ID = '_SECOND_LIGHT_INSTANCE_ID_'
createInstance(SECOND_LIGHT_INSTANCE_ID, {
// machine definition here and other stuffs
states: {
someState: {
on: {
EVENT: {
actions: sendToInstance(FIRST_LIGHT_INSTANCE_ID, { type: 'TOGGLE' })
}
}
}
}
})
Examples
What's next ?
The library can cover a lot of use cases and the most simple ones. But there's some improvements to come for it:
- useInstance
- useDataFromContext
- Add tests to the library
- Handle spawned machines
And other awesome stuffs.
✨
Contributors Thanks goes to these wonderful people (emoji key):
Axel Mohamed SADAT |
This project follows the all-contributors specification. Contributions of any kind welcome!
chore: `prepare` script implies dependecy to `yarn`
It seems that, as this repo uses
yarn
binary inprepare
script, it forces CI to install yarn globally in order to install this dependency. Asnpm
is always available on a node.js project, would you consider using npm instead?Source: https://github.com/moh12594/xstate-store/blob/main/package.json#L22
core(package.json): Make peer dependencies less restrictive
I have try to use you (very good thanks) library in a repo that uses react@^17.0.0 and get into peer dependency issues. As it seems that your library could work with react@^17.0.0, I make a proposal, include this version as valide peer dependency. Wdyt?