Skip to content

Commit

Permalink
Fixed an issue with some exit handlers being executed more than once …
Browse files Browse the repository at this point in the history
…when stopping a machine (#2895)
  • Loading branch information
Andarist authored Dec 27, 2021
1 parent 8435c5b commit df5ffce
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/perfect-paws-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with some exit handlers being executed more than once when stopping a machine.
31 changes: 11 additions & 20 deletions packages/core/src/StateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,21 +673,19 @@ class StateNode<
}

const subStateKeys = keys(stateValue);
const subStateNodes: Array<
StateNode<TContext, any, TEvent, TTypestate>
> = subStateKeys.map((subStateKey) => this.getStateNode(subStateKey));

subStateNodes.push(this);

return subStateNodes.concat(
subStateKeys.reduce((allSubStateNodes, subStateKey) => {
const subStateNode = this.getStateNode(subStateKey).getStateNodes(
stateValue[subStateKey]
);
const subStateNodes: Array<StateNode<TContext, any, TEvent, TTypestate>> = [
this
];

return allSubStateNodes.concat(subStateNode);
}, [] as Array<StateNode<TContext, any, TEvent, TTypestate>>)
subStateNodes.push(
...flatten(
subStateKeys.map((subStateKey) =>
this.getStateNode(subStateKey).getStateNodes(stateValue[subStateKey])
)
)
);

return subStateNodes;
}

/**
Expand Down Expand Up @@ -991,13 +989,6 @@ class StateNode<
}
}

if (!transition.source) {
transition.exitSet = [];

// Ensure that root StateNode (machine) is entered
transition.entrySet.push(this);
}

const doneEvents = flatten(
transition.entrySet.map((sn) => {
const events: DoneEventObject[] = [];
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/stateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function getAllStateNodes<TC, TE extends EventObject>(
export function getConfiguration<TC, TE extends EventObject>(
prevStateNodes: Iterable<StateNode<TC, any, TE, any>>,
stateNodes: Iterable<StateNode<TC, any, TE, any>>
): Iterable<StateNode<TC, any, TE, any>> {
): Set<StateNode<TC, any, TE, any>> {
const prevConfiguration = new Set(prevStateNodes);
const prevAdjList = getAdjList(prevConfiguration);

Expand Down
22 changes: 22 additions & 0 deletions packages/core/test/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,28 @@ describe('choose', () => {
expect(exitCalled).toBeTruthy();
expect(childExitCalled).toBeTruthy();
});

it('should call each exit handler only once when the service gets stopped', () => {
const actual: string[] = [];
const machine = createMachine({
exit: () => actual.push('root'),
initial: 'a',
states: {
a: {
exit: () => actual.push('a'),
initial: 'a1',
states: {
a1: {
exit: () => actual.push('a1')
}
}
}
}
});

interpret(machine).start().stop();
expect(actual).toEqual(['root', 'a', 'a1']);
});
});

describe('sendParent', () => {
Expand Down

0 comments on commit df5ffce

Please sign in to comment.