menu

Statecharts

Statecharts are a precise, visual way of modeling the behaviour of complex reactive systems. They describe how things work, and can help you get your code doing exactly what you want.

Channels
Team

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 5 months 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

March 18, 2020 at 7:43pm
XState actor management is not mature enough to handle deep communication
I'm not even convinced it ever should be.
Edited
like-fill
1
  • reply
  • like
Pass the state down through the actors.
I think it's "Pass the state up through the actors and down to the components." right?
  • reply
  • like
By the way, I'm working on a much simpler "foundation" for using the actor model in JS that XState V5 will make use of (in some way). Ideally, the process should be:
  1. Use the actor-model library (which I'm currently working on) as a very thin abstraction for organizing code/logic via the actor model
  2. Refine actor behaviors with XState
like-fill
5
  • reply
  • like
That's great , thank you for your work on this! Hopefully this FAQ can help curb some questions until then 👍
like-fill
2
  • reply
  • like
I think the problem with XState actors and React is the fact that XState isn't in charge of rendering to the DOM. In a way React components are also actors, messaging data and events up and down. Dare I say the React Context API is an event bus. Private state is kept private. Shared state is communicated via messages (callback props). Fitting a whole different actor system on top of that is hard.
  • reply
  • like
You could make it work if you made state machines produce React elements, share them with a parent machine that also produces a React element and combines it. E.g. a form machine could get all the input elements form child machines.
  • reply
  • like
React IS the actor system spawning machines. Rather than XState.
  • reply
  • like
React IS the actor system spawning machines. Rather than XState.
Yes, and once you come to the realization that "everything is an actor" (in one way or another), that becomes more clear. And that's why it's fine, and probably a little less messy, to let React do the cross-component communication and event-passing rather than XState (for now), since otherwise, React and XState are going to fight over who has control.
Maybe XState will win the fight one day ;-)
like-fill
3
  • reply
  • like

March 24, 2020 at 11:45pm
This is awesome. just getting started with actors. thank you.
like-fill
1
  • reply
  • like
So in essence, make a bunch of different machines (actors) and have react create them and bus messages either through useContext or with Redux like john's example.
Since components are actors, each machine is like the brain of each component, and react is the body. Then you have a little army of Machine/Components. :D.
  • reply
  • like

March 25, 2020 at 1:25pm
Right - components can be considered actors!
  • reply
  • like
.. and bus messages either through useContext or with Redux like john's example.
Or just prop drilling if components aren't to distant from each other.
like-fill
1
  • reply
  • like
Cooooool. This simplifies things a LOT. Much easier to work with.
  • reply
  • like

March 27, 2020 at 10:34pm
I made a (very) simple example!
I like it because it feels the same as calling useMachine in a component, you just call useContext instead of useMachine!
Edited
like-fill
2
  • reply
  • like

May 7, 2020 at 11:00am
The consensus seems to be that XState loses its framework-agnostic status once you start having to manage multiple actors then? I'm building a multi-player board game and had this dream of XState for managing game and player state and having React just render the thing.
  • reply
  • like
The consensus seems to be that XState loses its framework-agnostic status once you start having to manage multiple actors then?
Not exactly. At a certain point, you're fighting the framework over control of the state, since the framework wants to be control of more state. It's possible to use XState in a completely framework-agnostic way, but that may require doing things that aren't that idiomatic in that framework unfortunately.
  • reply
  • like

July 7, 2020 at 5:24am
Just came across this thread, and I'm terribly confused now...especially with the example codesandbox. Is this saying not to use actors (as in XState actors) at all, and just share one machine across components (either through props or context)?
  • reply
  • like

July 7, 2020 at 7:52pm
Correct. When using XState + ReactJS I've found using props or context to communicate between sibling- or hierarchically related components more efficient. Primarily because ReactJS is the "mother" framework here sitting on top of XState. The hierarchical concept of actors as children of machines does not translate one-on-one to a component hierarchy. E.g. when using actors, messages are sent from actors up to parent machines and from parent machines to parent components and back down to child components. Not from actors directly to nested child components.
  • reply
  • like
We're not saying "don't use actors". Only trying to clarify that actors don't directly communicate with components and only via the machine that spawned them.
  • reply
  • like

July 8, 2020 at 3:03am
I'm just trying to understand when you'd use actors. Let's say I'm creating a todo application, so the examples I've seen have you spawn a new actor for each "todo item" But instead you can "spawn" an actor by rendering a new component with it's own machine. Communication just happens by passing props down to that component, and the component sending events back up.
So where do actors fit in?
  • reply
  • like

July 8, 2020 at 1:36pm
you can "spawn" an actor by rendering a new component with it's own machine Exactly. The component is the actor here. We're just choosing not to use the XState specific API for spawning actors directly by a parent machine but rather letting ReactJS be the actor system here
  • reply
  • like
Out-of-the-box it is difficult to let actors spawned by machines be in charge of the UI/DOM when using ReactJS.
  • reply
  • like
Letting ReactJS be the system managing the messaging between systems just makes more sense
  • reply
  • like
Yeah I got that. My question is when do we use actors? I haven't yet used them yet, but twitter makes it seem like actors were a very big deal...perhaps a gamechanger in frontend architecture. I was wondering I was missing something? So is there a use case for actors? Do I not have to feel guilty that I haven't used them?
  • reply
  • like
I've been using XState extensively (with ReactJS) and I have yet to use actors, so no, you do not have to use actors.
  • reply
  • like
Show more messages