This little package provides an extension to the default interpreter class from xstate project.
Added functionality:
Allow functional actions (i.e. all non built-in actions) to raise
events with the interpreter via returned value, without having to have a reference
to an interpreter instance in advance.
Xstate is organized to keep Machine definition (states, context, actions, etc.) separate from interpreter that runs that machine. This allows for great insulation of logic but also is inconvenient is certain use cases.
In particular, when event is generated by machine in some fashion dynamically and therefore must be issued by a function that is a part of the machine definition.
In the original Xstate spec, state transition is happening by following mechanics:
- Event is external and is fed to the machine by interpreter (invoking send() or sendTo() of the default interpreter).
- Event raised by one of the built-in actions - send(), raise().
- One event can produce different transitions by implementing conditional transitions (guards).
The trouble comes when you need to have different transitions based on information that is internal the machine (state + context). Namely, if you want to raise the event from your action's code.
Lets consider a machine:
---> B
I --> A --|
---> C
After transitioning to A from an external event you want the machine to end up in either state B or C, based on some internal information (context) and incoming event.
One solution would be to use guards intensively with Null Events and onEntry actions, but that involves a lot of duplication and is just messy. onEntry actions process event's side-effects, since guards should be pure, duplication comes from the fact that every guard must evaluate conditions independently, so if logic is not some straightforward value check, you either repeat it for every guard or you save intermediary calculations into context in some onEntry action - messy.
Even then, there's a particular (common) scenario where guards won't work - if the choice of the transition is delayed. For example, after reaching state A, the machine makes an async request (to database, for example) and then decides on which transition to take based on the results of the request.
Another solution is to make a choice inside A's onEntry action and then invoke send() with the appropriate event on the interpreter. The obvious downside is that with this route the action function must have a way to reference the instance of the interpreter. Which in practice means it have to rely on some global variable.
This package provides a solution for both of those scenarios.
Regular and straightforward.
npm install --save @trulyacerbic/xstate.actionable-interpreter
Then in the code replace import of the default interpreter with the one from this package:
Replace...
const { Interpreter } = require('xstate/lib/interpreter');
... with
const { Interpreter } = require('@trulyacerbic/xstate.actionable-interpreter');
In your action function, return the event you want interpreter to raise as a return value:
- return an object, a string or a number - the definition is exactly the same as the standard event. This event will be put to the internal queue of the interpreter and raised after all current actions executed.
- return a Promise. When and if this promise successfully resolves, the resolving value will be used as a new event to be send to the interpreter.