Skip to content

Commit

Permalink
Prevent action replay (#3926)
Browse files Browse the repository at this point in the history
* Do not restore actions

* Add changeset

* move test

* Discard actions in restoreState

* Adjust changeset

* Remove link

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
davidkpiano and Andarist committed Apr 7, 2023
1 parent e001520 commit f9f692b
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 32 deletions.
7 changes: 7 additions & 0 deletions .changeset/hip-melons-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'xstate': major
---

Restored state will no longer contain actions, since they are assumed to have already been executed. Actions will not be replayed.

If you want to replay actions when restoring state, it is recommended to use an event sourcing approach.
2 changes: 2 additions & 0 deletions packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ export class StateMachine<
}
});

restoredState.actions = [];

return restoredState;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/test/after.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ describe('delayed transitions', () => {
expect(spy).not.toHaveBeenCalled();
});

it('should execute an after transition after starting from a state resolved using `machine.getInitialState`', (done) => {
// TODO: figure out correct behavior for restoring delayed transitions
it.skip('should execute an after transition after starting from a state resolved using `machine.getInitialState`', (done) => {
const machine = createMachine({
id: 'machine',
initial: 'a',
Expand Down
55 changes: 24 additions & 31 deletions packages/core/test/interpreter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,43 +105,34 @@ describe('interpreter', () => {
}, 100);
});

// https://github.com/statelyai/xstate/issues/1174
it('executes actions from a restored state', (done) => {
const lightMachine = createMachine(
{
id: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: {
target: 'yellow',
actions: 'report'
}
}
},
yellow: {
on: {
TIMER: {
target: 'red'
}
it('does not execute actions from a restored state', () => {
const reportSpy = jest.fn();
const lightMachine = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: {
target: 'yellow',
actions: reportSpy
}
},
red: {
on: {
TIMER: 'green'
}
},
yellow: {
on: {
TIMER: {
target: 'red'
}
}
}
},
{
actions: {
report: () => {
done();
},
red: {
on: {
TIMER: 'green'
}
}
}
);
});

const currentState = 'green';
const nextState = lightMachine.transition(currentState, {
Expand All @@ -154,6 +145,8 @@ describe('interpreter', () => {

const service = interpret(lightMachine, { state: restoredState });
service.start();

expect(reportSpy).not.toHaveBeenCalled();
});

it('should not execute actions that are not part of the actual persisted state', () => {
Expand Down
19 changes: 19 additions & 0 deletions packages/core/test/rehydration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,23 @@ describe('rehydration', () => {
expect(actual).toEqual(['active', 'root']);
});
});

it('should not replay actions when starting from a persisted state', () => {
const entrySpy = jest.fn();
const machine = createMachine({
entry: entrySpy
});

const actor = interpret(machine).start();

expect(entrySpy).toHaveBeenCalledTimes(1);

const persistedState = actor.getPersistedState();

actor.stop();

interpret(machine, { state: persistedState }).start();

expect(entrySpy).toHaveBeenCalledTimes(1);
});
});

0 comments on commit f9f692b

Please sign in to comment.