You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In v4 we used exit actions on the root of the machine to handle clean-up if the invoked machine was ended because the parent machine transitioned away from the state containing the invoke. This worked well and seemed sensible to me, though it has a wrinkle I hadn't noticed where if the child machine hits a state with type: "final" set that root-level exit action will not be run which is surprising.
In v5 it looks like the opposite happens? When the parent machine leaves the state which invoked the child machine the root-level exit action is not run. However, if the child machine gets to a type: "final" state then the root-level exit action will be run.
I don't see anything calling out this behavior change in the migration docs, but based on other discussions (#4801) this seems intended?
In v5 in order to have some code run in a machine no matter how it is stopped, what's the best approach? To capture the "parent leaves state" case I seem to need to use a fromCallback()-based invoke so that I can do cleanup stuff by returning a function. To capture the "child is done" state I seem to need to have a top-level exit action (unless the parent machine transitions via the invoke.onDone, in which case the invoke cleanup will happen).
Is there a singular way to run some code when a machine is done, no matter how it happens? Right now this feels a bit fragile, as I probably need both a fromCallback() and an root-level exit but also I need to make sure they don't both run.
import{createMachine,createActor,sendTo,fromCallback}from"xstate";constchild=createMachine({initial: "one",exit: ()=>console.log("child.exit-action"),invoke: [{id: "exit-invoke",src: fromCallback(()=>()=>console.log("child.exit-invoke")),},],on: {go: ".two",},states: {one: {},two: {type: "final",},},});constparent=createMachine({initial: "one",on: {go: ".two",send: {actions: sendTo("child",{type: "go"}),},},states: {one: {invoke: {id: "child",src: child,},},two: {invoke: {id: "child",src: child,onDone: "three",},},three: {},},});console.log("== STARTING ==");constactor=createActor(parent);actor.subscribe((state)=>console.log("parent:",state.value));actor.start();console.log("= Ending child by leaving parent state =");actor.send({type: "go"});console.log("= Ending child by entering child state with type: final =");actor.send({type: "send"});
Result
== STARTING ==
parent: one
= Ending child by leaving parent state =
child.exit-invoke
parent: two
= Ending child by entering child state with type: final =
child.exit-action
child.exit-invoke
parent: two
parent: three
import{createMachine,interpret,send}from"xstate";constchild=createMachine({initial: "one",exit: ()=>console.log("child.exit-action"),invoke: [{id: "exit-invoke",src: (context,event)=>(dispatch)=>()=>console.log("child.exit-invoke"),},],on: {go: ".two",},states: {one: {},two: {type: "final",},},});constparent=createMachine({initial: "one",on: {go: ".two",send: {actions: send({type: "go"},{to: "child"}),},},states: {one: {invoke: {id: "child",src: child,},},two: {invoke: {id: "child",src: child,onDone: "three",},},three: {},},});console.log("== STARTING ==");constactor=interpret(parent);actor.subscribe((state)=>console.log("parent:",state.value));actor.start();console.log("= Ending child by leaving parent state =");actor.send({type: "go"});console.log("= Ending child by entering child state with type: final =");actor.send({type: "send"});
Result
== STARTING ==
parent: one
= Ending child by leaving parent state =
child.exit-action
child.exit-invoke
parent: two
= Ending child by entering child state with type: final =
parent: three
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
In v4 we used
exit
actions on the root of the machine to handle clean-up if the invoked machine was ended because the parent machine transitioned away from the state containing theinvoke
. This worked well and seemed sensible to me, though it has a wrinkle I hadn't noticed where if the child machine hits a state withtype: "final"
set that root-levelexit
action will not be run which is surprising.In v5 it looks like the opposite happens? When the parent machine leaves the state which invoked the child machine the root-level
exit
action is not run. However, if the child machine gets to atype: "final"
state then the root-levelexit
action will be run.I don't see anything calling out this behavior change in the migration docs, but based on other discussions (#4801) this seems intended?
In v5 in order to have some code run in a machine no matter how it is stopped, what's the best approach? To capture the "parent leaves state" case I seem to need to use a
fromCallback()
-basedinvoke
so that I can do cleanup stuff by returning a function. To capture the "child is done" state I seem to need to have a top-levelexit
action (unless the parent machine transitions via theinvoke.onDone
, in which case theinvoke
cleanup will happen).Is there a singular way to run some code when a machine is done, no matter how it happens? Right now this feels a bit fragile, as I probably need both a
fromCallback()
and an root-levelexit
but also I need to make sure they don't both run.v5 example
Live on CodeSandbox
Result
v4 example
Live on CodeSandbox
Result
Beta Was this translation helpful? Give feedback.
All reactions