xstate: Persisting Parallel State Machines Fails

Description I’m trying to repeatedly externally step a parallel state machine with persistence. I have set up a simple linear machine that goes a -> b -> c -> d and notes entry and exit.

The base machine is

    states: {
      a: {
        entry: ["doEntry"],
        exit: ["doExit"],
        on: { TRIGGER: { target: "b" } }
      },
      b: {
        entry: ["doEntry"],
        exit: ["doExit"],
        on: { TRIGGER: { target: "c" } }
      },
      c: {
        entry: ["doEntry"],
        exit: ["doExit"],
        on: { TRIGGER: { target: "d" } }
      },
      d: { type: "final" }
    }

In the code sandbox I set up 4 tests.
I create a parallel machine which uses two instance of the base machine.

  1. Simple Linear works as expected.
  2. Simple Parallel also works as expected. You see two entrys to a, two exits of a… etc
  3. Step Linear Works, but complains about Warning: No implementation found for action type I would like to know why that is the case, but the output looks correct
  4. Step Parallel does not work.

For quick reference, the serialize/deserialize code is snipped below:

function step(machine, jsonState) {
  const service = interpret(machine);

  if (jsonState === null) {
    service.start();
  } else {
    var restoredState = State.create(JSON.parse(jsonState));
    service.start(restoredState);
    service.send("TRIGGER");
  }

  var retVal = JSON.stringify(service.state);
  service.stop();
  return retVal;
}

function parallelStepTest (machine) {
  console.log('start')
  jsonState.b = step(machine, null)
  console.log('step 1')
  jsonState.c = step(machine, jsonState.b)
  console.log('step 2')
  jsonState.d = step(machine, jsonState.c)
}

Expected Result

Step Parallel
start
entry a
entry a
step 1
exit a
exit a
entry b
entry b
step 2
exit b
exit b
entry c
entry c
step 3
exit c
exit c

Actual Result

Step Parallel
start
entry a
entry a
step 1
entry a
entry a
exit a
entry b
exit a
entry b
step 2
step 3

Reproduction

Additional context

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 2
  • Comments: 19 (7 by maintainers)

Most upvoted comments

@davidkpiano I/We appreciate it. XState is impressive work. I would think wanting to persist complex states (and store them in a database) is a probably pretty common use case. Maybe if the documentation added an example of the "right way"™ to take a complex machine and persist/rehydrate, it would go a long way.

As an example, the one at the bottom of https://xstate.js.org/docs/guides/parallel.html has hierarchy and parallel machines

Either way, thanks for creating XState. It’s impressive work.

I appreciate it, thank you! Persisting complex states should “just work” ideally.

Has anyone successfully achieved this? I’m having a lot of trouble restoring child states correctly.

I created a new version of the code sandbox which captures the issue I’m having here: https://codesandbox.io/s/xstate-parallel-machine-persist-ktjtw

It appears to correctly restore the state for step 1… however on step 2 and further it hits console.log('no service for ' + name) because the call to service.start(resolvedState) does not create the service.children map.

If anyone has a simple example of recursive machine re-hydration, or a suggestion as to why service.start isn’t creating the children I’d greatly appreciate it.

Thanks.

Hello @Andarist, thanks for following up.

Yes, I would expect to be able to serialize the complex machine at any state and then resume it at a later point.

Thanks for the tip there. I didn’t realize that capturing the state of a machine wouldn’t include the child states. I would expect it would do so. Is there a case where you might want to save a parent state without capturing the child states?

Either way, I will investigate recursively descending into child properties to save/restore state. If you or anyone else has an example of this, I would appreciate it very much.

Thanks again!