Spectrum is now read-only. Learn more about the decision in our official announcement.


The Statecharts community on Spectrum is (along with spectrum) MOVING TO OTHER PLATFORMS: For statecharts discussions in general go to Statecharts Discussion on GitHub (link) or Gitter (link). For XState-specific questions, go to the XState discussion forum for Q&A or the Stately discord chat to chat.


FAQ for Managing Hierarchy of XState Actors in React

March 18, 2020 at 7:05pm

FAQ for Managing Hierarchy of XState Actors in React

March 18, 2020 at 7:05pm (Edited 3 years ago)
Reminder: Actors have completely private state and only communicate with messages.
How does this model fit into React and XState. Here are my recommendations right now from experimenting, as of xstate 4.7.6 and xstate-react 0.8.0. Please respond if you have found better solutions.. these are just a summary of my findings I thought I'd share.
tldr; Until actors in XState are more mature, the best way to communicate between shared services is not to use XState’s action types except for simple and shallow parent-child relationships. Instead, directly communicate with message passing between services managed by React components.

Should I spawn actors inside of XState or use the React model?

XState actor management is not mature enough to handle deep communication or other patterns like event busses. So, use React for now. Let React spawn actors and build the parent/child tree naturally by components.
  • You can use React Context for referencing parent state.
  • If a child needs to send a message to a parent, put that logic in callback props passed to the child i.e. onChange.
  • If a parent needs to send a message to a child, the child machine should subscribe itself to the parent and configure how it reacts to parent events.
  • If parallel actors needs to communicate with one another, they should all subscribe to a common event bus to configure how they respond. Then, the components can send events to the bus which will to forward them along to other parallel actors. You might need some helpers so that parallel actors can subscribe to only certain events from the event bus (like those with a certain ID).

How do I simplify sending messages to actors? Should I send to an event bus, Observable, actor, etc?

Do what Elixir does. They use actors in many of their libs, but most developers wouldn’t know it. They hide the message passing behind simple, straightforward APIs that handle the message passing for them. Do this with React hooks that use machines behind the scenes. Do this with React components by using callbacks.

How do I keep services decoupled but communicating? Where does all this wiring up happen?

Use decoupling strategies like event busses, Observables, and callbacks. Pass these into your machines. Keep actors ignorant of each other, and a channel (event bus) or API can be the smart one to hook them up. Custom React hooks can handle wiring up up these imports and translating prop changes into events.

What about deep message passing??

Use an event bus/channel that both top level and deep level actors subscribe to, and send your messages to the event bus to notify all relevant actors. XState machines can forward events to these channels, or an API level can redirect relevant events to the channels instead of the service.

What about deep state access i.e. user state deep in a screen??

XState does not have a great way of broadcasting state access to all children. Some ideas on how deep actors or components can access great-great-great-great-grandparent actor's context.
NOTE: most deep state access is for the purpose of UI, not state logic. If it is needed in state logic, the state and its updates can usually be represented by an event including the relevant state.
  1. Use React Context. Expose the top level actor/service through Context. Your child actors will need to be spawned by React instead of inside of XState, but that might be worth it until XState’s actor model matures. Since most state like this just needs to be used for display/rendering (not inside other machines), React will handle updates to it automatically. If you do need to update the state inside of a service, just use React hooks to send an UPDATE event when it changes.
  2. Use Observable streams. Instead of manually wiring up specific request/respond events, you can pass a stream around to always get the latest value. Instead of an event bus moderating, you’d use a stream. The parent component would hook up its context to the stream, and then the deep child component would import the stream through React context or a global registry like Redux/singleton.
  3. Use an event bus for requesting latest state. There would be some boilerplate, but you could hook up to an event bus that uses events to request a state from the publisher, and the publisher would send a response to all subscribers. Remember, the child would not know the state by default until it requested it. Since XState is not smart enough yet to communicate between services/actors started outside of XState, we can use an event bus and manually hook up the events. This is unfortunate boilerplate if the data is just used for rendering, but some helper code could simplify. NOTE: this could be an Observable to simplify.. see above.
  4. Use XState. Pass the state down through the actors. When the state is updated (like a User Setting change, you would manually propagate the USER_CHANGE event down through your child actors). However, this introduces the highest coupling, and it’s a poor representation of a much shallower relationship between the top-state and the child that needs it. Think about passing down an Observable hooked up to the parent's context if you’re limited to only managing machine through XState.
Show previous messages

July 10, 2020 at 1:51pm
I've also resolved this by forwaring incoming callback props via event payloads E.g. A component that receives a callback prop, say props.onContinue, renders:
<SubmitButton onClick={() => send({type: 'SUBMIT', onContinue: props.onContinue }) />
That gives the machine access to props.onContinue rather than using useEffect. The machine might invoke an asynchronous service and in it's onDone configuration access evt.onContinue
Yes but there is a problem with that solution . If your machine transitions to error or success state after on submit event and you need to consume the onContinue function only in the success state then you need to assign the onContinue in context in order to consume it .
Not necessarily. You can propagate the callback to the done or error event
states: {
submitting: {
invoke: {
src: (_, evt) => Promise.resolve(42).then(n => [n, evt.onContinue]
Which would result in a DoneInvokeEvent<[number, () => void]>
Yes but dont you think that if they states are not close to each other then you have a "callback" drilling ?

July 16, 2020 at 1:37pm
Since it's been a while when this was posted, I do believe that XState's actor communication has matured quite a bit. With respond, forward and escalate as well as the pureaction creator being introduced, there is quite a lot we can do with plain XState without the need to invoke a lot of machines from within React. I'm usually only invoking the top most actor from within React and have had 6-7 level deeply nested actors without the need for an event bus. Sometimes I use explicit event forwarding e.g I want to send an event from A to C. B may define the same event as C and just has an action "forwardEventToC".
It does get more tricky when you need to route an event from A to C5 to E12 assuming that some actors like B and D spawn multiple child actors of the same type. In this case, you'd need to include the ids as well and at some point I would too reach for an event bus solution as proposed here. However, thus far I didn't have the need to do so.
TL;DR: Try to reach the limits of plain XState message passing before going for more complex solutions.

October 7, 2020 at 4:52pm
I'm looking to build XState-first components, where as much as possible of the business logic resides in XState & the FrontEnd framework will be extremely light & easy to replace.
Does anyone have good examples of being able to spawn n-number of children that have 2-way communication between parent & children please? (Children won't talk to each other.)
I'm still researching other places; here is what I found:
You'd want to come up with some architecture that abstracts view/dom updates away from the machines. Similar to how react and react-dom are I think

October 8, 2020 at 1:50pm
The more I think about this though the less convinced I am that this is something we need. All the state machines in my apps are heavily influenced by the ux/ui design, not just business logic. They are already decoupled from React since all my components just receive data and callback props.

October 10, 2020 at 8:49pm
Couldn't we create a RootContext that holds references to all actors, then any children can access this. It does break encapsulation a bit for the children components, but seems to be the most straight forward approach

October 12, 2020 at 7:25am

August 2, 2021 at 9:16pm
Hi everyone, could you please update on whether the advice in this FAQ is still current as of today? I haven't seen any recent mentions about XState v5 or how will it change the actor model for a while (I might not have been searching enough), and if it were to be a complete game changer it would be good for new adopters to have a brief idea on how is that going to work by now. I have seen that there is a new library "xactor", but I haven't seen anyone talking about it, so I don't know if that will be the new way to go instead of xstate v5. I reckon that Actors are the main pain point for react developers willing to adopt xstate, not knowing exactly what is the recommended way to replace the classic redux-centered architecture with a xstate-centered architecture. From what I can gather from this thread, I might be wrong, but seems like most communication problems could be fixed by implementing an event bus? (derekduncan) (chautelly) (davidkpiano)