statelyai / xstate

Actor-based state management & orchestration for complex app logic.
https://stately.ai/docs
MIT License
26.87k stars 1.23k forks source link

After actions don't trigger when rehydrating state using State.from #294

Closed Dirklectisch closed 5 years ago

Dirklectisch commented 5 years ago

Bug or feature request?

Bug

Description:

I am trying to resume an interpreter using a state as the argument when starting. However the after actions on that state do not trigger leaving my interpreter stuck in that state.

(Bug) Expected result:

State transition after the specified time.

(Bug) Actual result:

Interpreter waiting for further input.

(Bug) Potential fix:

I haven't had a chance to look at the source code for Xstate, but I did find a very dirty workaround. I added an extra loading state to my machine that I will load up first and then transition into the actual state I want to restore to. This way everything works as expected.

Link to reproduction or proof-of-concept:

Here's a code sandbox that illustrates the problem:

Edit xstate state after actions on rehydrate

Here's the actual code of my minimal example for easy reference:

import { Machine, State } from "xstate";
import { interpret } from "xstate/lib/interpreter";

const stateChart = {
  context: {},
  states: {
    ping: {
      after: {
        1000: "pong"
      }
    },
    pong: {
      after: {
        1000: "ping"
      }
    }
  }
};

const machine = Machine(stateChart);
const service = interpret(machine);
service.onTransition(current => console.log(current.value));

const initState = State.from("ping", {});
service.start(initState);
davidkpiano commented 5 years ago

When a delayed transition occurs, a special action is internally sent to send a timer event after the specified delay.

However, when you specify the initState, you aren't specifying any actions. To fix this:

  1. Define an initial state (which you should do anyway)
  2. Use the machine.initialState as the initial state (this is the default when calling service.start()).

The modified code below works:

import { Machine, State } from "xstate";
import { interpret } from "xstate/lib/interpreter";

const stateChart = {
  context: {},
  initial: "ping",
  states: {
    ping: {
      after: {
        1000: "pong"
      }
    },
    pong: {
      after: {
        1000: "ping"
      }
    }
  }
};

const machine = Machine(stateChart);
const service = interpret(machine);
service.onTransition(current => console.log(current.value));
service.start();
Dirklectisch commented 5 years ago

@davidkpiano Sure it works if you don't pass an initial state to the interpreter and set the initial up front. However that wasn't the issue here. I want to be able to enter the state machine with a specific context at any point of the machine when restoring the system. Therefore I'm not using the initial state but State.from.

What if I wanted to start the above machine on the pong state state with an arbitrary context? Now the delayed transition on that wouldn't trigger.

Thanks for your reply!

davidkpiano commented 5 years ago

What if I wanted to start the above machine on the pong state state with an arbitrary context? Now the delayed transition on that wouldn't trigger.

There's two approaches to this: