-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: a React-specific interpreter hook #1094
Comments
I'm very interested in this area and thought about this extensively. However, I don't quite see right now why we'd like, for example, to delay the execution of particular actions. Machines, even if tied to some component, are still living beside components - it's like a parallel system in my head (which only integrates with React). Queuing etc also makes things a lot harder to reason about and I'm not sure if it's worth the trouble. Note also that machines tied to components should mainly respond to user interactions and even when using plain React you don't expect the handler for, let's say, |
Yeah, I think I see your point. I don’t really think of it as “delaying” the actions, but it would definitely have that effect some (or a lot) of the time. I don’t know how often in practice it’s important that side effects fire immediately, though. In the case you gave, where an event gets sent by a user interaction, the state and/or context would still be updated at a high priority (almost synchronously). Would that not be enough to satisfy the user that gave the input? What other actions could that transition produce that need to run at a high priority (or else)? The ability to reason about the interpreter’s behavior is also definitely a real concern. That might become easier as our mental model of the concurrent mode APIs gets better, but it also might not. |
@davidkpiano Just saw |
Already done. Check out |
That's really cool! Looking over the This is the branch, right? |
Yep! That's the branch. Still a couple things left to fix, though. |
Cool stuff! Definitely a lot less extreme than reimplementing |
Added to Roadmap |
Bug or feature request?
Technically a feature request, but I'm about to make a bunch of uninformed claims about React's concurrent mode, so let's call it a proposal draft discussion.
Description:
Once React's concurrent mode is released,
@xstate/react
should look into including auseMachine
-style React hook (let's call ituseInterpret
) that doesn't use anInterpreter
instance internally, and instead interprets a machine using React's scheduling primitives. The API can be similar (const [state, send] = useInterpret(machine)
), but internally, thestate
is stored in React state,send
is implemented with a pure state transition inside asetState
function, and actions are executed fromuseEffect
.Motivation:
In React's new concurrent mode, changes to state are scheduled rather than immediately executed, any of those changes might potentially be discarded, and side effects are only run for changes that are "committed". This works because React's state is fully immutable and all state changes are made through pure update functions. On the other hand, the best way to represent a complex statechart in a React component right now is by using an
Interpreter
, which contains mutable state, and executes every single event sent to it (using its own scheduler, which React is unaware of).This doesn't mean that
Interpreter
s aren't compatible with concurrent mode. The React team seems interested in bridging the gap between themselves and libraries that use mutable state withuseMutableSource
. That would probably work fine with XState, but that wouldn't allow React the optimizations of discarding or re-ordering updates based on priority (e.g. if a user clicks a button in the middle of some other work).By using React primitives to interpret a statechart, and sticking totally to the "rules" of React, I'd imagine that a user could get the most possible benefits from concurrent mode.
Potential pitfalls:
useMutableSource
just ends up being better than this once we actually get to play with it.interpret()
might be confusing.(Feature) Potential implementation:
I have a rough implementation down below in a codesandbox, but the overall gist goes like this:
useInterpret
hook has an internal state object with the latest machine state and a queue of actions to execute. Callingsend
schedules a state update where a new machine state is created from the old one, the new state's actions are bound to that new state, and those actions are pushed to the action queue without being called. This way, the new state, including the side effect functions, is a pure function of the previous state and the given event.useEffect
(which runs after React has "committed" to a certain state), all the bound actions that haven't been called before are called in the order they were added to the queue. Then an update is scheduled to wipe the queue.useRef
object, which is only read from and written to by theuseEffect
mentioned above.Link to reproduction or proof-of-concept:
https://codesandbox.io/s/pedantic-tdd-nqbgx
(lots of this code was copied from
interpreter.tsx
in this repo)The text was updated successfully, but these errors were encountered: