Giter Site home page Giter Site logo

statelyai / xstate Goto Github PK

View Code? Open in Web Editor NEW
26.2K 189.0 1.2K 228.71 MB

Actor-based state management & orchestration for complex app logic.

Home Page: https://stately.ai/docs

License: MIT License

JavaScript 0.65% TypeScript 97.63% Vue 0.88% Svelte 0.46% HTML 0.09% CSS 0.28%
state-machine statechart interpreter visualizer scxml state state-management workflow orchestration hacktoberfest

xstate's Introduction


XState logotype
Actor-based state management & orchestration for complex app logic. → Documentation

XState is a state management and orchestration solution for JavaScript and TypeScript apps. It has zero dependencies, and is useful for frontend and backend application logic.

It uses event-driven programming, state machines, statecharts, and the actor model to handle complex logic in predictable, robust, and visual ways. XState provides a powerful and flexible way to manage application and workflow state by allowing developers to model logic as actors and state machines.

✨ Create state machines visually in Stately Studio → state.new


📖 Read the documentation

➡️ Create state machines with the Stately Editor

🖥 Download our VS Code extension

📑 Inspired by the SCXML specification

💬 Chat on the Stately Discord Community

✍️ Browse through the many XState examples

Templates

Get started by forking one of these templates on CodeSandbox:

Template

🤖 XState Template (CodeSandbox)

Open in StackBlitz

  • XState v5
  • TypeScript
  • No framework

⚛️ XState + React Template (CodeSandbox)

Open in StackBlitz

  • React
  • XState v5
  • TypeScript

💚 XState + Vue Template (CodeSandbox)

Open in StackBlitz

  • Vue
  • XState v5
  • TypeScript

🧡 XState + Svelte Template (CodeSandbox)

Open in StackBlitz

Super quick start

npm install xstate
import { createMachine, createActor, assign } from 'xstate';

// State machine
const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  context: {
    count: 0
  },
  states: {
    inactive: {
      on: {
        TOGGLE: { target: 'active' }
      }
    },
    active: {
      entry: assign({ count: ({ context }) => context.count + 1 }),
      on: {
        TOGGLE: { target: 'inactive' }
      }
    }
  }
});

// Actor (instance of the machine logic, like a store)
const toggleActor = createActor(toggleMachine);
toggleActor.subscribe((state) => console.log(state.value, state.context));
toggleActor.start();
// => logs 'inactive', { count: 0 }

toggleActor.send({ type: 'TOGGLE' });
// => logs 'active', { count: 1 }

toggleActor.send({ type: 'TOGGLE' });
// => logs 'inactive', { count: 1 }
  • Visually create, edit, and collaborate on state machines
  • Export to many formats, including XState v5
  • Test path & documentation autogeneration
  • Deploy to Stately Sky
  • Generate & modify machines with Stately AI
XState Viz

state.new

Why?

Statecharts are a formalism for modeling stateful, reactive systems. This is useful for declaratively describing the behavior of your application, from the individual components to the overall application logic.

Read 📽 the slides (🎥 video) or check out these resources for learning about the importance of finite state machines and statecharts in user interfaces:

Packages

Package Description
🤖 xstate Core finite state machine and statecharts library + interpreter
📉 @xstate/graph Graph traversal utilities for XState
⚛️ @xstate/react React hooks and utilities for using XState in React applications
💚 @xstate/vue Vue composition functions and utilities for using XState in Vue applications
🎷 @xstate/svelte Svelte utilities for using XState in Svelte applications
🥏 @xstate/solid Solid hooks and utilities for using XState in Solid applications
@xstate/test Model-Based-Testing utilities (using XState) for testing any software
🔍 @xstate/inspect Inspection utilities for XState

Finite State Machines

CodeStatechart
import { createMachine, createActor } from 'xstate';

const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {
      on: {
        TIMER: 'red'
      }
    },
    red: {
      on: {
        TIMER: 'green'
      }
    }
  }
});

const actor = createActor(lightMachine);

actor.subscribe((state) => {
  console.log(state.value);
});

actor.start();
// logs 'green'

actor.send({ type: 'TIMER' });
// logs 'yellow'
CodeStatechart
Finite states
Open in Stately Studio

Hierarchical (Nested) State Machines

CodeStatechart
import { createMachine, createActor } from 'xstate';

const pedestrianStates = {
  initial: 'walk',
  states: {
    walk: {
      on: {
        PED_TIMER: 'wait'
      }
    },
    wait: {
      on: {
        PED_TIMER: 'stop'
      }
    },
    stop: {}
  }
};

const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {
      on: {
        TIMER: 'red'
      }
    },
    red: {
      on: {
        TIMER: 'green'
      },
      ...pedestrianStates
    }
  }
});

const actor = createActor(lightMachine);

actor.subscribe((state) => {
  console.log(state.value);
});

actor.start();
// logs 'green'

actor.send({ type: 'TIMER' });
// logs 'yellow'

actor.send({ type: 'TIMER' });
// logs { red: 'walk' }

actor.send({ type: 'PED_TIMER' });
// logs { red: 'wait' }
Hierarchical states
Open in Stately Studio

Parallel State Machines

CodeStatechart
import { createMachine, createActor } from 'xstate';

const wordMachine = createMachine({
  id: 'word',
  type: 'parallel',
  states: {
    bold: {
      initial: 'off',
      states: {
        on: {
          on: { TOGGLE_BOLD: 'off' }
        },
        off: {
          on: { TOGGLE_BOLD: 'on' }
        }
      }
    },
    underline: {
      initial: 'off',
      states: {
        on: {
          on: { TOGGLE_UNDERLINE: 'off' }
        },
        off: {
          on: { TOGGLE_UNDERLINE: 'on' }
        }
      }
    },
    italics: {
      initial: 'off',
      states: {
        on: {
          on: { TOGGLE_ITALICS: 'off' }
        },
        off: {
          on: { TOGGLE_ITALICS: 'on' }
        }
      }
    },
    list: {
      initial: 'none',
      states: {
        none: {
          on: {
            BULLETS: 'bullets',
            NUMBERS: 'numbers'
          }
        },
        bullets: {
          on: {
            NONE: 'none',
            NUMBERS: 'numbers'
          }
        },
        numbers: {
          on: {
            BULLETS: 'bullets',
            NONE: 'none'
          }
        }
      }
    }
  }
});

const actor = createActor(wordMachine);

actor.subscribe((state) => {
  console.log(state.value);
});

actor.start();
// logs {
//   bold: 'off',
//   italics: 'off',
//   underline: 'off',
//   list: 'none'
// }

actor.send({ type: 'TOGGLE_BOLD' });
// logs {
//   bold: 'on',
//   italics: 'off',
//   underline: 'off',
//   list: 'none'
// }

actor.send({ type: 'TOGGLE_ITALICS' });
// logs {
//   bold: 'on',
//   italics: 'on',
//   underline: 'off',
//   list: 'none'
// }
Parallel states
Open in Stately Studio

History States

CodeStatechart
import { createMachine, createActor } from 'xstate';

const paymentMachine = createMachine({
  id: 'payment',
  initial: 'method',
  states: {
    method: {
      initial: 'cash',
      states: {
        cash: {
          on: {
            SWITCH_CHECK: 'check'
          }
        },
        check: {
          on: {
            SWITCH_CASH: 'cash'
          }
        },
        hist: { type: 'history' }
      },
      on: { NEXT: 'review' }
    },
    review: {
      on: { PREVIOUS: 'method.hist' }
    }
  }
});

const actor = createActor(paymentMachine);

actor.subscribe((state) => {
  console.log(state.value);
});

actor.start();
// logs {
//   value: { method: 'cash' },
// }

actor.send({ type: 'SWITCH_CHECK' });
// logs {
//   value: { method: 'check' },
// }

actor.send({ type: 'NEXT' });
// logs {
//   value: 'review',
// }

actor.send({ type: 'PREVIOUS' });
// logs {
//   value: { method: 'check' },
// }
History state
Open in Stately Studio

Sponsors

Special thanks to the sponsors who support this open-source project:

Transloadit Logo

SemVer Policy

We understand the importance of the public contract and do not intend to release any breaking changes to the runtime API in a minor or patch release. We consider this with any changes we make to the XState libraries and aim to minimize their effects on existing users.

Breaking changes

XState executes much of the user logic itself. Therefore, almost any change to its behavior might be considered a breaking change. We recognize this as a potential problem but believe that treating every change as a breaking change is not practical. We do our best to implement new features thoughtfully to enable our users to implement their logic in a better, safer way.

Any change could affect how existing XState machines behave if those machines are using particular configurations. We do not introduce behavior changes on a whim and aim to avoid making changes that affect most existing machines. But we reserve the right to make some behavior changes in minor releases. Our best judgment of the situation will always dictate such changes. Please always read our release notes before deciding to upgrade.

TypeScript changes

We also reserve a similar right to adjust declared TypeScript definitions or drop support for older versions of TypeScript in a minor release. The TypeScript language itself evolves quickly and often introduces breaking changes in its minor releases. Our team is also continuously learning how to leverage TypeScript more effectively - and the types improve as a result.

For these reasons, it is impractical for our team to be bound by decisions taken when an older version of TypeScript was its latest version or when we didn’t know how to declare our types in a better way. We won’t introduce declaration changes often - but we are more likely to do so than with runtime changes.

Packages

Most of the packages in the XState family declare a peer dependency on XState itself. We’ll be cautious about maintaining compatibility with already-released packages when releasing a new version of XState, but each release of packages depending on XState will always adjust the declared peer dependency range to include the latest version of XState. For example, you should always be able to update xstate without @xstate/react. But when you update @xstate/react, we highly recommend updating xstate too.

xstate's People

Contributors

aleksejdix avatar andarist avatar annaghi avatar audionerd avatar carloslfu avatar chenxsan avatar chlbri avatar codingdive avatar d4rekanguok avatar davidkpiano avatar davkato avatar dependabot[bot] avatar edsilv avatar farskid avatar github-actions[bot] avatar goldingaustin avatar hnordt avatar jamesopstad avatar jenny-tru avatar laurakalbag avatar mattpocock avatar mogsie avatar nlopin avatar santicros avatar silverwolf90 avatar studnitz avatar sveltemaster avatar tombyrer avatar vantanev avatar woutermont avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xstate's Issues

Dot notation for transitioning to substates is confusing

Hi there,

SCXML doesn't seem to use the dot-notation for transitioning to sub-states. Is there a reason why xstate doesn't do the same?

SCXML:

<state id="foo">
  <state id="fooSub">
  </state>
</state>
<state id="bar">
  <transition event="barEvent" target="fooSub" /> <!-- here xstate requires `foo.fooSub` instead of just `fooSub` -->
</state>

It seems that having unique-global state names allows also for substates to transition to their parent siblings, or actually any other parent state; described also in this other issue #52

<state id="foo">
  <state id="fooSub">
    <transition event="fooSubEvent" target="bar" /> <!-- not sure xstate allows this? -->
  </state>
</state>
<state id="bar">
  <transition event="barEvent" target="fooSub" />
</state>

Thoughts?

Support activities natively

The original Statecharts paper has an entire chapter devoted to actions and activities. An excerpt:

An activity always takes a nonzero amount of time, like beeping, displaying, or executing lengthy computations. Thus, activities are durable-they take some time-whereas actions are instantaneous. In order to enable statecharts to control activities too, we need two special kinds of actions to start and stop activities. Accordingly, with each activity X we associate two special new actions, start(X) and stop(X), and a new condition active(X), all with the obvious meanings.

This hasn't carried forward in SCXML, SCION or xstate; there are hooks to start and stop things, but the notion of activities has been lost, at least it's not modeled anywhere.

The paper describes an activity as, e.g. "beeping" when in the "beeping" state, so the common solution is simply to use the name of the active state(s) to determine which UI to show. This (IMHO) leads to brittleness and the inability to do any sort of refactoring of the statechart (e.g. introduce substate) without considering how that new state might affect the UI.

Example state definition

loading: {
  on: { results: "show" },
  active: [ "http_request", "loading_ui" ],
}
  • I'm not sure about bikeshedding activities or activity or active. I prefer "active" since it's shortest, and more declarative-feeling.
  • A string can be passed instead of an array, when there's only one activity.

The user of the statechart would use http_request to start the activity of making a HTTP request, while the loading_ui would cause the UI to display some "loading" state.

This could then easily be refactored to e.g. delay the "loading" view by half a second.

loading: { on: { results: next },
  active: "http_request",
  states: {
    loading_1: {
      after: {
        0.5: loading_2
      } 
    },
    loading_2: {
      active: "loading"
    }
  }
}

You could then also easily delay the exit of the loading state by half a second, so that the loading state doesn't flash by specializing the loading_2 state.

Usage:

State gets a new property activities with three properties:

  • start — an array of strings indicating which activities that need to start
  • stop — an array of strings indicating which activities that need to start
  • active — an array of strings indicating which activities that are currently active
newstate = transition(state, event, extradata);
// in the first example
console.log(newstate.activities.start); // [ "http_request", "loading" ]
// in the second example (after refactoring
console.log(newstate.activities.start); // [ "http_request" ]
// "after 0.5 seconds"
console.log(newstate.activities.start); // [ "loading" ]
console.log(newstate.activities.active); // [ "http_request", "loading" ]
// when results arrive, both activites are stopped.
console.log(newstate.activities.start); // [ ]
console.log(newstate.activities.stop); // [ "loading", "http_request" ]

Like entry/exit handlers, the activities started and stopped should be listed in the same order: When entering states, activites are started from the outermost states inward, and conversely when exiting states, activites are stopped from the innermost going outwards.

It paves the way for guard conditions that check if some activity is ongoing, preferably without needing to use a function declaration, but that could be food for a separate issue. I'm thinking something along the lines of is_active(loading)

This is not be a breaking change

Tjhis is not part of the SCXML specification, activites aren't mentioned in the spec. It is probably possible to convert activities as described here into pure entry/exit actions that trigger start/stopping of activities, but this becomes very verbose, and therefore probably not used so much.

Question: how would this live side-by-side with a router?

Thanks for an inspiring talk! I'm fascinated with the idea of expressing UIs as automata and I'm really keen to take xstate on a test run in a small project. I'm wondering, though, as to how I would integrate this state machine with a route hierarchy.

Each route, I imagine, would be an immediate entry point to a default state associated with it... perhaps the transitions would also transition the route state where appropriate... would each route contain its own state machine, orthogonal from each other route's state machine? How would this nest?

I would love to hear your opinions on the topic!

Restore machine state from localStorage

I hope its fine to ask about an implementation detail … Would it be possible to restore the machine state from localStorage (e.g. when refreshing the site)? If yes would be simplest way to do it?

Can't trigger two parallel actions simultaneously

I'm trying to trigger two transitions like this:

  trigger = (action) => {
    this.setState(
      stateMachine.transition(this.state, action).value
    );
  }

 this.trigger('ACTION_1');
 this.trigger('ACTION_2');

But it will only execute the first, unless I wrap the second in a setTimeout.

Thing is: given that these two actions are not conflicting because they act in different paralleled states, is there a reason I should not be doing that or is it something that can be improved in xstate?

Prettier configs

It may be a good idea to include a prettier config file or put it in the package.json. I use atom and prettier was changing the files but I didn't know which settings to use so I turned it off.

Thoughts on Features

Hi there,

Per your comment in #3 I'll share some feature related thoughts here in a "meta-issue" fashion.

It would be good to have a mode (debug?) where transition failure returns more than false and maybe a more classic node callback style (err, result) pair with a detailed error about what caused the transition failure (source state doesn't exist, target state doesn't exist, event not recognised,...).

Better error messages? A missing state now generates TypeError: transition.targetState.initialStates is not a function.

Would be good to document your non-documented exports (stateReducer, mapState,...) and give some short examples (probably reusing the green, yellow, red example). I'm not sure how I would use mapOnEntry and mapOnExit for instance. It's great that you have curried versions of these functions and giving some example of that might help share some functional goodness :)

I'm kicking the tires for an event sourcing application, so I found myself needing a fold time function, i.e. left monadic fold over a list of events/actions, to rebuild the current state. Maybe another candidate for a utility function? ( // state -> [ event ] -> state)

Because of this type of application, performance will potentially matter, so it might be good to have some tests which generate large event sequences and help check that new features don't introduce to much performance penalties.

Maybe more longterm, it might be good to follow the fantasy-land spec and provide functor and monad instances?

Thanks a ton for the lib!

Jun

onExit Order

Right now, onExit behaves slightly odd. If you have multiple nested states with onExit hooks, the first item in exit is the outermost state. I'd expect xstate to leave the innermost state first.

const currentExit = ['OUTER_EXIT', 'INNER_EXIT']
const expectedExit = ['INNER_EXIT', 'OUTER_EXIT']

onEntry firing on nested transition in hierarchical setting

Bug

In a hierarchical state graph onEntry action is fired on a solely nested transition although it is not defined in the nested graph.

Expected result:

onEntry only fires on entry to a state or on transition to itself but not on a transition of a nested sub graph.

Actual result:

onEntry action fires on every transition of nested state graphs.

Example:

This example generates a nested fooBar machine inside the ping state of a pingPong state machine.

const pingPong = Machine({
  initial: 'ping',
  states: {
    ping: {
      onEntry: ['entryEvent'], 
      on: {
        TICK: 'pong'
      },
      initial: 'foo',
      states: {
        foo: {
          on: {
            TACK: 'bar'
          }
        },
        bar: {
          on: {
            TACK: 'foo'
          }
        },
      },
    },
    pong: {
      on: {
        TICK: 'ping'
      }
    },
  }
});

Transition from nested state ping.foo to ping.bar on TACK fires entryEvent of the pingPong machine. Although we stay inside of the state and thus should not have the entryEvent fired.

pingPong.transition('ping.foo', 'TACK')
// => State {
//   value: { ping: 'bar' },
//   actions: [
//     'entryEvent' // WHY?! 
//   ]
// }

[Feature] Interpreter (event emitter)

Related to #36 cc. @mogsie

Provide a separate interpreter which will act as a simple event emitter for which to run a machine. This is technically side-effectful, but it is not a core part of how xstate works (re. the transition function) nor is it required. The developer should always be allowed to implement their own runner.

Proposed API

  • Interpreter(machine: Machine) returns an event emitter:
  • interpreter.on(eventName: string, callback: (state: State) => void)
    • possibly interpreter.onTransition(callback), etc.
  • interpreter.off(callback) removes all listeners for that callback
  • interpreter.dispatch(event: Event) dispatches events (object/string) to machine
  • interpreter.init() starts the interpreter on the initial state
import { Machine, Interpreter } from 'xstate';

const myMachine = Machine({ ... });
const interpreter = Interpreter(myMachine);

const eventListener = (state: State) => { ... };

// add event listener
interpreter.on('transition', eventListener);
// or interpreter.onTransition(eventListener); perhaps

// remove event listener
interpreter.off(eventListener);

// dispatch event to statechart
interpreter.dispatch({ type: 'FOO', data: 123 });

// start interpreter on initial state
interpreter.init();

Related to the proposed delayed actions syntax, we could have something like:

const machine = Machine({
  initial: 'foo',
  states: {
    foo: {
      onEntry: [
        { type: 'TIMER', delay: 4000 } // send 'TIMER' event after 4s
      ]
    }
  }
});

const interpreter = Interpreter(machine);
interpreter.on('action', a => console.log(a));
interpreter.init();

// logs { type: 'TIMER', ... } after 4s

Usage with a reactive library, such as RxJS:

// ... from first example
const listen = handler => interpreter.on('state', handler);
const unlisten = handler => interpreter.off(handler);

const state$ = fromEventPattern(listen, unlisten);

state$.subscribe(e => console.log(e));

interpreter.init();

Thoughts? Very open to feedback on this one.

(Discussion) Potential API for states

This is a placeholder issue to keep track of the different public APIs for exposing states. There needs to be an API for determinate/nondeterminate finite automata, and for the string and object representation of them.

machine.transition('green', 'TIMER');
// NFA (string) => ['yellow']
// DFA (string) => 'yellow'
// DFA (object) => { value: 'yellow', states: {} }

machine.transition('yellow', 'TIMER');
// NFA (string) => ['red.pedestrian.walk']
// DFA (string) => 'red.pedestrian.walk'
// DFA (object) => { value: 'red', state: { value: 'pedestrian', state: { value: 'walk' }}}

Is there a way to fire actions without transitioning (i.e. when creating a new object)?

My team is using xstate to manage state transitions on the back end.

I have actions working well when transitioning between states, but I also need to trigger some side effects when creating new objects (usually one of the side effects that is already defined in my actionMap).

Assuming there's no built-in way to do this, I'm thinking it would make sense for us to add a dedicated new initialState to our machine, then create new objects in two steps:

  1. Create an object with the new state.
  2. Transition immediately to the actual desired state, using this transition to fire any necessary actions.

Side Note: It would be great if the project had a dedicated Gitter channel linked from the GitHub README and the doc site.

[Feature] Serializable guard (string conditions)

Description:

Hi,
I've been thinking about serializable guard (i'm loading my statechart from server).
Does Function object (mdn link) could work for that ?

usage of Function :

const add = new Function ("a", "b", "return a + b")

add(2, 3)
// --> 5

Potential implementation:

The implementation could look like :

// ...
const condFn = !cond
  ? () => true // we can go forward if we don't have guard
  : typeof cond === 'string'
    ? new Function('extendedState', 'eventObject', cond) // create the function if cond is a string
    : cond; // use directly otherwise

if (condFn(extendedState, eventObject)) {
// ...

usage of cond :

const searchMachine = Machine({
  initial: 'idle',
  states: {
    idle: {
      on: {
        SEARCH: {
          searching: {
            // only transition to 'searching' if cond is true
            cond: "return extendedState.canSearch && eventObject.query && eventObject.query.length > 0"
          }
        }
      }
    },
    searching: {
      onEntry: ['executeSearch']
      // ...
    },
    searchError: {
      // ...
    }
  }
});

Advantage

  • simple to implement
  • doesn't have to create something custom
  • good browser support
  • keep function working

Drawback

  • implicit call of return inside the string.
  • can't change the extendedState & eventObject variable name.
  • can't guaranty user doesn't call an global object (example : return window.foo > 3).

npm install xstate Cannot find module 'xstate'

{
  "name": "test-xstate",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "xstate": "^1.0.2"
  },
  "devDependencies": {
    "jest": "^20.0.4"
  }
}

and running

var xstate = require('xstate');

describe('test', function () {
  it('install xstate', function () {
    expect(xstate).not.toBe(undefined);
  });
});

leads to Cannot find module 'xstate'

Transition trigger on nested event?

First off, thanks for the library and excellent CSS-Tricks articles. They've really improved how I structure my state management. I'm restructuring a site with this new approach, but I'm running into unexpected behavior with nested states. My understanding is that a state should only transition to it's next state on events defined in it's on object. Using the Light Machine example, the "green" state only transitions in response to "TIMER" and "POWER_OUTAGE" events.

lightMachine.transition("green", "TIMER") // "yellow"
lightMachine.transition("green", "POWER_OUTAGE") // "red"

However, as illustrated in this Codepen, it seems to also fire a transition on the "PED_COUNTDOWN" event. I see that a recent breaking change results in transition always returning a state. Why? I would expect that any event not specified in the on object of a state or it's parent states should be ignored, and that no state would be returned from a transition if no state transition occurred.

lightMachine.transition("green", "BROKEN") // "Error: Machine 'light' does not accept event "BROKEN"
lightMachine.transition("green", "PED_COUNTDOWN") // "green", expect no response or error

For context, the reason I'm curious is for animated page navigation. In order to properly make page transitions work, you need to keep a history of states navigated to so that popstate can be implemented correctly. Barba.js has some problems doing this consistently and correctly, which is why xstate seems more promising.

Support delayed events

Hi. One of the things I like about statecharts is to deal with delaying events and so on, introducing small wait states that process events differently.

Since xstate is a pure functional thing it doesn't make sense to support delayed events per se, but it would be mighty useful if it were possible to

  1. annotate a state with a delayed event
  2. show (with an example) how one might code it up (using setTimeout)

One thing is that one would have to know if an event actually exited a state, since exiting a state should cancel the associated timer.

I suggest something a lot simpler than scxml (which doesn't have this built in) (yaml format)

states:
  searching:
    on:
      results: displaying_results
    onEntry: startHttpRequest
    onExit: cancelHttpRequest
    states:
      silent:
        after: 2s warning
      warning:
        onEntry: showWarning

or something like that. It's important that the timeout doesn't need a name, just a target.

As a user of this I would have to know that a transition to searching state should start a timer, so I can act accordingly, but I also then need to know when I handle an event if I actually exited the searching state at all. Today this might be visible in the returned state's "history", but I would have to inspect the machine myself to know if the new state / historical state have a common ancestor.

E.g. in the state machine above, if I'm in the "searching" "silent" state then an event might exit silent, and I would have to know that this happened, and I think it's hard to infer from the current response

Thank you

Hi David,

Just watched your React Rally talk. Great talk and really liking xstate. I am already using it in an app of mine. Works like a charm. Also adopted the React Router like api for conditionally rendering of components from this screencast on xstate. Do you have any thoughts on that? Anyway, just wanted to say thank you for this. Looking forward to see how this idea is going to evolve.

— Ferdinand

Add strict mode

Ref: MicheleBertoli/react-automata#3

Errors will be thrown:

  • On unknown states
  • On unknown actions
  • When a state does not handle an action*

* There's two types of "no-change" transitions:

// no transition should take place
foo: { on: { NOCHANGE: undefined } }

// should transition to self
foo: { on: { SELFCHANGE: 'foo' } }

Possible immutability issue?

Bug or feature request?

Possible bug

Description:

I'm trying to create a little helper library to include actions, based off the example in the docs: http://davidkpiano.github.io/xstate/docs/#/guides/complete

export default (config: MachineConfig, actions?: StateXActions) => {
  const machine: StandardMachine|ParallelMachine = Machine(config);

  let currentState: StateValue = machine.initialState.value;

  const extendedState = {};

  const dispatch = (event: Event) => {
    const nextState = machine.transition(currentState, event, extendedState);

    nextState.actions
      .map(action => typeof action === 'string' ? action : action.type)
      .filter(key => actions && actions[key])
      .forEach(key => actions![key](event, dispatch));

    currentState = nextState.value;
  };

  return {
    currentState,
    dispatch,
    machine,
    state: extendedState,
  };
};

And trying to test with this:

test('dispatching go should enter go state', () => {
    const store = createStore(mocks.minimal);
    store.dispatch({ type: 'go' });
    expect(store.currentState).toEqual('go');
  });

mocks.minimal:

{
    initial: 'start',
    states: {
      start: {
        on: {
          go: 'go',
        },
      },
      go: {
        on: {
          stop: 'start',
        },
      },
    },
  },

(Bug) Expected result:

I'm expecting that store.currentState will be updated with the next value.

(Bug) Actual result:

The test fails:

Expected value to equal: "go"
Received: "start"

(Bug) Potential fix:

I'm wondering if this is an issue with immutability. I've added the code to a repo here: https://github.com/andyjessop/statement if you want to reproduce.

Update npm package, please

I need this 0e6aac4
but I cannot fetch it without building myself

Is there any way to push the build to npm?

Thanks!

   { modified: '2018-02-05T16:18:38.536Z',
     created: '2017-05-24T05:00:41.121Z',
     '0.0.1': '2017-05-24T05:00:41.121Z',
     '1.0.0': '2017-08-24T04:55:42.055Z',
     '1.0.2': '2017-09-01T14:31:13.496Z',
     '1.1.0': '2017-09-14T21:04:05.582Z',
     '1.2.1': '2017-09-14T21:48:46.392Z',
     '2.0.0': '2017-11-22T14:09:12.260Z',
     '2.0.1': '2017-12-01T15:23:32.502Z',
     '2.0.2': '2017-12-06T04:45:10.336Z',
     '2.0.3': '2017-12-10T05:43:52.891Z',
     '2.1.0': '2017-12-10T05:47:21.336Z',
     '3.0.0': '2017-12-23T20:14:27.207Z',
     '3.0.1': '2018-01-08T06:24:44.510Z',
     '3.0.2': '2018-01-18T17:14:31.968Z',
     '3.0.3': '2018-01-24T15:30:07.321Z',
     '3.0.4': '2018-02-05T16:18:38.536Z' },

package built wrong

When referencing the package using the import statement:
import { Machine } from 'xstate'

It says it can't find xstate. If I open the npm package.json found here:
node_modules/xstate/package.json

and change line 5 to this:
"main": "dist/xstate.js",

That error goes away. Maybe there is a better fix, but hopefully that gives you enough info, here is the package.json after I changed it:
package copy.txt

. Thanks!

How to handle parallel nested state

Hey David,

I'm playing with xtate and I'm wondering how to handle the parallel state below.

{
  key: "machine",
  parallel: true,
  states: {
	lights: {
	  initial: "on",
	  states: {
		on: {
		  on: { TOGGLE: "off" },
		  initial: "green",
		  states: {
			green: {
			  on: {
				TIMER: "yellow"
			  }
			},
			yellow: {
			  on: {
				TIMER: "red"
			  }
			},
			red: {
			  on: {
				TIMER: "green"
			  },
			  initial: "walk",
			  states: {
				walk: {
				  on: {
					PED_TIMER: "wait"
				  }
				},
				wait: {
				  on: {
					PED_TIMER: "stop"
				  }
				},
				stop: {}
			  }
			}
		  }
		},
		off: {
		  on: { TOGGLE: "on" }
		}
	  }
	},
	animation: {
	  initial: "on",
	  states: {
		on: {
		  on: {
			TOGGLE: "off"
		  }
		},
		off: {
		  on: {
			TOGGLE: "on"
		  }
		}
	  }
	}
  }
}

Calling machine.initialState returns a shallow object:

{ lights: 'on', animation: 'on' }

But subsequent calls to machine.transition(...).value returns nested objects.

{
  lights: {
     on: 'green'
  },
  animation: 'on'
}

and

{
  lights: {
     on: {
       red: 'walk'
    }
  },
  animation: 'on'
}

Shouldn't initialState returns { lights: 'on.green'}?
And calling .value shouldn't return { lights: 'on.red.walk' }

Is this the expected behavior?

[Feature] State metadata

Especially in testing and analytics, it can be useful for states to have associated metadata. Below is a simple proposal for what this can look like, analogous to the <datamodel> specified in SCXML:

const machineConfig = {
  initial: 'foo',
  states: {
    foo: {
      on: { ... },
      data: { ... } // metadata for 'foo' state
    },
    // ...
  },
  data: { ... } // metadata for machine (considered a state node)
}

And state objects will have the represented immutable metadata:

machine.transition(...);
State {
  value: 'foo',
  history: ...,
  actions: [...],
  data: { ... } // state.data or undefined
}

The data property would be a record that can have any meta data in it that does not affect the transitions of the state machine (i.e., it is internal to the machine). Examples include:

  • passing heuristics for automatically generated tests to "know" when an externally-defined machine is in a certain state
  • adding analytical data to states (useful for weighted state machines)
  • static data useful for rendering any given state
  • whatever you want!

Thoughts? The name data was chosen instead of meta to maintain consistency with SCXML. cc. @mogsie @MicheleBertoli

[Feature] Transient states/transitions

Best explained in Horrock's book:

Transient states are different from normal states because none of the transitions leaving such
a state is triggered by events. Instead, the transitions are simply conditions without an
associated event. Since there are no events associated with transitions leaving the state, on
entry to the state, the conditions on the event arrows are evaluated and the next state is
entered immediately: hence the transient nature of the state. In effect a transient state delays
the entry to the next state until the actions associated with an event have been executed.
Transient states are not part of the original statechart notation, but they are needed when
designing user interfaces that retrieve data from a database. They can also be used to
simplify designs.

Below is an example of a transient state, and how it can be useful:

Transient State Diagram

Proposed API:

Instead of explicitly defining a "transient state", we allow any state to implicitly become transient by introducing transient transitions, which are checked immediately upon entering the state. This has the same semantics of transient states as described in the book. The transient transition (or "immediate transition") will be based on an empty event string, since that best demonstrates that it comes from no event:

UPDATED: see #43 (comment)

// const updateMachine = Machine({
//   initial: 'G',
//   states: {
//     G: {
//       on: { UPDATE_BUTTON_CLICKED: 'E' },
//     },
//     E: {
//       on: {
//         // transient transition
//         '': {
//           D: { cond: ({ data }) => !data }, // no data returned
//           B: { cond: ({ status }) => status === 'Y' },
//           C: { cond: ({ status }) => status === 'X' }
//         }
//       }
//     },
//     D: {},
//     B: {},
//     C: {}
//   }
// });

Actions will be evaluated in this order (using the above example):

  1. onExit for G
  2. actions for G -> E (ON_BUTTON_CLICKED)
  3. onEntry for E
  4. actions for determined transient transition from E -> D (or B or C)
  5. onExit for E
  6. onEntry for determined next state (D or B or C)

And a transition will evaluate to the ultimately determined state:

const nextState = updateMachine.transition('G', 'UPDATE_BUTTON_CLICKED', {});
console.log(nextState.value);
// => 'D'

Thoughts? Feedback?

xstate monitor

Hey @davidkpiano,

I'm interesting in instrumenting xstate and I need hooks to bind to. Like for example an API that will allow me to monitor what's going on with the machines. Imagine a tool that shows you in real time what's going on and draws a graph for example. An API like this.

[Feature] Support local transitions

Feature

Description:

A local transition is a transition from a state to its substate which sidesteps exiting the source state, since (because the target is a child state) after the transition, the new configuration will include the source state.

The opposite, an external transition would be a transition to ones children that exits the current state and enters it again.

Potential implementation:

What the API would look like

A transition can already be expressed as plain strings or objects, so I suggest these local transitions be specified in the object form only:

on: {
  EVENT: { target: "foo", type: "local" }
}

The default would probably be an external event, as that's probably backwards compatible, however local events (where possible) do make sense to consider them being local by default.

Perhaps "internal" should be used to be more in line with SCXML? Local stems form Wikipedia and UML.

This is not a breaking change

statecharts.github.io describes the concept: https://statecharts.github.io/glossary/local-transition.html — It includes a workaround for xstate that more or less gives you the same result.

Self transition prevent next transition

Hi there,

I might be misunderstanding something but adding a self transition seems to prevent other transitions with the same starting state to occur. For example this properly results in the closed state:

let review = machine(`
  init -> opened (OPEN)
  opened -> closed (CLOSE)
  closed!
`);

let newState = review.transition('opened', 'CLOSE');
// => 'closed' as expected

However the below returns false:

let review = machine(`
  init -> opened (OPEN)
  opened <- (UPDATE)
  opened -> closed (CLOSE)
  closed!
`);

let newState = review.transition('opened', 'CLOSE');
// => 'closed' as expected

// => false

Cheers,

Jun

Initial states aren't executed

If you have a statechart that defines an initial state, then when a machine "starts" it enters the state. This means that the effects of that initial state should be able to get a hold of.

For a normal state transition, you pass in the current state, and an event, and you get back (among other things) the new state, and the side effects of any exits and entrys that happen.

However, when you are starting out, you haven't even entered that initial state. There should be a way to get to that initial state, and any side effects. Passing in 'undefined' as the current state might be a way, since there is no event, and no initial state. So perhaps transition() with no args should return that initial State instance.

XState's "scope"

Hello,
I came here after watching David's interesting video: https://www.youtube.com/watch?v=VU1NKX6Qkxc
I'm unclear about something and did not find a better place than here to ask.
The above mentioned presentation may lead one to believe XState is meant to help with managing the state of the whole app (Global state). Especially when David talks about graphs versus trees and how it's more natural to "touch" the state of a different component/node directly from another node instead of having to go up/down the "tree" (See video at precisely: https://www.youtube.com/watch?v=VU1NKX6Qkxc&feature=youtu.be&t=449 and about half a minute onward)
However, what I personally took out from that video is the idea that XState might help ease state management of a single component (Possible even a component and its children components) by using the DST "paradigm" and making state transition "explicit" and declarative.
Please someone explain if XState is meant for "App's global state management". Some example maybe... or explanation?
Thank you for any feedback.

v3 fails to install (postinstall)

Using yarn to install 3.0.0:

Exit code: 2
Command: npm run build && npm run build:utils
Arguments:
Directory: ....\node_modules\xstate
Output:
> [email protected] build ....\node_modules\xstate
> tsc && webpack -p ./lib/index.js ./dist/xstate.js --config webpack.config.js

error TS2688: Cannot find type definition file for 'mocha'.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] build: `tsc && webpack -p ./lib/index.js ./dist/xstate.js --config webpack.config.js`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?```

How to determine current (non-parallel) state logically?

I must be missing something, but I'm not seeing an out-of-the-box way of determining the current state logically (e.g., in a switch statement). Imagine this statechart:

screen shot 2018-02-23 at 3 16 07 pm

  • machine.initialState will be "logIn"
  • If I trigger transition LOG_IN the state returned is {"logIn": "loggingIn"}

This is quite reasonable for a human to interpret, but programmatically it makes me wonder if I'm supposed to perform typeof state == 'string' tests, and then for (let name in state) if it's an object to extract the current state name.

Am I missing a utility or property that can let me do the following?

const topLevelStateName = ?;
switch (topLevelStateName) {
  case 'logIn':
    handleTheLogInStuff();
    break;
  case 'home':
    handleTheHomeStuff();
    break;
}

To clarify, here's sort of what I'd expect (let's pretend this code doesn't know when/if the statechart changes):

let state = machine.initialState;
if (Math.random() < 0.5) {
  // To demonstrate that we don't want to know if FSM has progressed or not.
  state = machine.transition(state, 'LOG_IN'); // who knows where this might go?
}
if (state[0] == 'logIn') {
  // We're still handling login.
  // Based on the chart above, state would be one of the following:
  // ["logIn", "input"]
  // ["logIn", "loggingIn"]
}

Of course, if you take this to be a proposal, let's come up with a non-breaking change and not what I showed above. :)

Missing declaration file for module 'xstate'

I installed the latest version 1.2.1 to try xstate in a new project written in typescript and I'm getting an issue when importing something from the module.

I use this import statement as indicated in the README

import { Machine } from 'xstate';

But in the TS compiler output, I get "ERROR in src/app/app.machine.ts(1,25): error TS7016: Could not find a declaration file for module 'xstate'. '/home/stephane/Libertrip/ModelV2/node_modules/xstate/dist/xstate.js' implicitly has an 'any' type."

It seems the built library lacks a declaration file to allow imports of all members using the module name.
One possible workaround is to specify the path to the file declaring the member

import xstate from 'xstate/lib/index';

Then I gain access to xstate.Machine which is correctly typed.

For convenience, would it be possible to fix this?

xstate as a storybook addon?

Hello folks,
Was wondering if any here has some experience with storybook ? I'm just looking at it and I read it has a "plugin facility". It seems one can "easily" add such plugins and augment/improve the experience.
[https://storybook.js.org/addons/addon-gallery/]
Would it possible or even desirable to have an extra tab in storybook for instance to display the state machine chart? Maybe clicking on a state makes the machine transition to that state?
Any thoughts? Especially those of you who have some experience with storybook (I don't, just started playing with it few minutes ago). Would something like the above be really useful?

XState + Mobx/Redux

Hi,
I was wondering how this library is intended to work together with other state management tools.
I am not really experience with any of these tools, but from my understanding and your comments in #15, I assume that you could use xState to handle the immutable part of your state, while using another form of state management for the rest of your state.

Based on that I tried to modified the example provided in the Readme and refactored it to use a Mobx Store instead of setState. I would be really interested in your opinion about the general implementation approach and if you like I can add the example to the Readme with some comments.

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { observable, action, useStrict, runInAction } from 'mobx'
import { Machine } from 'xstate'

useStrict(true)

const ROOT_URL = `https://api.github.com/users`
const myMachine = Machine({
  initial: 'idle',
  states: {
    idle: {
      on: {
        CLICK: 'loading'
      }
    },
    loading: {
      on: {
        RESOLVE: 'data',
        REJECT: 'error'
      }
    },
    data: {
      on: {
        CLICK: 'loading'
      }
    },
    error: {
      on: {
        CLICK: 'loading'
      }
    }
  }
})

class AppState {
  @observable data = {}
  @observable dataState = 'idle'
  @observable input = ''

  commands = {
    loading: this.searchRepositories
  }

  @action
  transition (action) {
    console.log(this.dataState, action)
    const newState = myMachine.transition(this.dataState, action).value
    const command = this.commands[newState]

    this.dataState = newState

    if (command) {
      command.call(this)
    }
  }

  @action
  async searchRepositories (){
    try {
      const data = await fetch(`${ROOT_URL}/${this.input}`).then(response => response.json())
      runInAction(() => {
        this.data = data
        console.log(this.data)
        this.transition('RESOLVE')  
      })
    } catch (error) {
      runInAction(() => {
        this.transition('REJECT')
      })
    }
  }

  @action changeText(text) {
    this.input = text
  }
}

const store = new AppState()

@observer
class App extends Component {
  render() {
    const { data, dataState } = store
    const buttonText = {
      idle: 'Fetch Github',
      loading: 'Loading...',
      error: 'Github fail. Retry?',
      data: 'Fetch Again?'
    }[dataState]
    return (
      <div>
        <input
          type="text"
          value={store.input}
          onChange={e => store.changeText(e.target.value)}
        />
        <button
          onClick={() => store.transition('CLICK')}
          disabled={dataState === 'loading'}
        >
          {buttonText}
        </button>
        {dataState === 'error'?
          <h1>Error!!!</h1> :
          data && <div>{JSON.stringify(data, null, 2)}</div>}
      </div>
    )
  }
}

export default App

Actions as functions in addition to strings and objects?

Bug or feature request?

Feature Request

Description:

Since functions are first-class citizens in JavaScript, and in the interest of reducing boilerplate, I suggest that function actions would be more ergonomic than strings or objects (in many cases).

(Feature) Potential implementation:

  • What the API would look like:

New Syntax:

const initTimer = seconds => () => {
  // use seconds to initiate a timer
}

const machine = Machine({
  initial: '1',
  states: {
    '1': {
      on: { 
        timer: '2'
      },
      onEntry: initTimer(30)
    },
    '2': {
      on: { 
        timer: '3'
      },
      onEntry: initTimer(45)
    },
    '3': {
      on: { 
        timer: '1'
      },
      onEntry: initTimer(60)
    }
  }
})

Current Syntax:

const actionMap = {
  initTimer: ({ seconds }) => {
    // use seconds to initiate a timer
  }
}

const machine = Machine({
  initial: '1',
  states: {
    '1': {
      on: { 
        timer: '2'
      },
      onEntry: { type: 'initTimer', seconds: 30 }
    },
    '2': {
      on: { 
        timer: '3'
      },
      onEntry: { type: 'initTimer', seconds: 45 }
    },
    '3': {
      on: { 
        timer: '1'
      },
      onEntry: { type: 'initTimer', seconds: 60 }
    }
  }
})
  • If this is a breaking change
    I don't see this as a breaking change. Actions are already polymorphic (either string or object), and it's the user's responsibility to determine what type an action is and what to do with it / how to handle it. In effect, this is likely to be a documentation change plus a ts change to allow actions of type function. This would signal to users that actions can be functions and that this construction is specifically supported by project maintainers.

  • If this is part of the existing SCXML specification
    It's a variation on the definition of actions in the SCXML spec.

Some thoughts & ideas

Hi @davidkpiano, I wanted to thank you for doing the node conf talk and leading the way with Harel Statecharts (I really enjoyed reading the book that you cited). Here I want to list some thoughts I've had to get some general feedback. I'll go ahead and close this issue after I open it. (I'm not saying any of these ideas should necessarily be part of this module)

One alternative API could be to separate out states and events into their own sections. Nested states could go in a "where" property. I think there are some intuitive advantages to this format, but the preference might be subjective. For example:

const paymentMachine = Machine({
  initial: 'method',
  states: ['method', 'review', 'paid'],
  events: {
    NEXT: ['method', 'review'],  // [from, to]
    PAY: ['review', 'paid'],
    PREV: ['review', 'method.$history']
  },
  where: {
    method: {
      initial: 'cash',
      states: ['cash', 'check'],
      events: {
        SWITCH: [ ['check', 'cash'], ['cash', 'check'] ] // [ [from, to], [from, to] ]
      }
    }
  }
})

Also, an easy way to do parallel states would be to represent the initial value as an object of booleans: initial: {notLoading: true, method: {check: true}}.

Have you thought about general approaches around tying these statecharts into your app data? A couple possibilities I've considered are:

  • Use state machines as simple properties within your existing app data. Eg. you have a User object/model that has a state machine property for its registration/session/confirmation/etc statuses
  • Use parameterized state machines where you store data inside the machine itself. You could have transition functions that take an argument and update the machine's data on a state transition. I think this is towards UML territory? 💀

The two above approaches are inverses of eachother, and both put the state machine in your app data layer. I was also thinking that state machines may best belong in your presentation and layout layer. In this case, they could be built into your functions for generating DOM elements. A strange idea..

const paymentProcess = elem('div', {
  machine: {
    initial: 'method',
    states: ['method', 'review', 'paid'],
    events: {
      NEXT: ['method', 'review'],  // [from, to]
      PAY: ['review', 'paid'],
      PREV: ['review', 'method.$history']
    }
  }
}, [ // children go here

 // Payment method selector
 elem('div', {
   class: {visible: 'method'}, // has this class when in this state
   machine: {
     initial: 'cash',
     states: ['cash', 'check'],
     events: {
       SWITCH: [ ['check', 'cash'], ['cash', 'check'] ] // [ [from, to], [from, to] ]
     }
   }
 }, [ 
   elem('p', state => 'selected ' + state),
   elem('button', {on: {click: 'SWITCH'}}, 'select cash'),
   elem('button', {on: {click: 'SWITCH'}}, 'select check')
 ]),

 // Review screen 
 elem('div', { class: {visible: 'review'} }, [
   elem('button', {on: {click: 'PREV'}}, 'go back'),
   elem('button', {on: {click: 'PAY'}}, 'pay!'),
 ]),

 // Paid
 elem('div', { class: {visible: 'paid'} }, [
   elem('p', "you're paid up!")
 ])
])

I'm unsure how the above would integrate with your app data. My ideal architecture would look something like:

  • Write statecharts that describe your UI behavior -- this can map pretty closely to the layout of your page
  • Write data types that describe your data layer (eg. a User) -- these should be totally unbound to your layout and need not be nested underneath any elements.
  • Map statechart transitions from anywhere on the page to updates in your data layer.
  • Map values from your data layer to content anywhere on the page.

To give some context, I have been working on a project called Uzu where I've been thinking of working in Harel Statecharts as the primary event mechanism. As you can see, the current design uses a simple event emitting object and is very bottom-up and event-driven.

[QUESTION] Guards

Hey @davidkpiano

Just watched your amazing talk at React Rally.

We currently use https://github.com/winzou/state-machine as a state machine in a php application, the package has a concept of guards, so if I try and call a transition it will first check to see if it passes the guard, if so continue and if not then stop execution. Is this something you could see being implemented in this package?

Nested initial states

Hey David,

This project is really interesting. Great work! I have a question though.

With the following machine (example from the readme) the machine.initialState will be method.

{
  initial: 'method',
  states: {
    method: {
      initial: 'cash',
      states: {
        cash: { on: { SWITCH_CHECK: 'check' } },
        check: { on: { SWITCH_CASH: 'cash' } }
      },
      on: { NEXT: 'review' }
    },
    review: {
      on: { PREVIOUS: 'method.$history' }
    }
  }
}

However I was expecting it to be method.cash. This is further solidified by the first transition you give in the example:

const checkState = paymentMachine
  .transition('method.cash', 'SWITCH_CHECK');

Can you explain why that is the case?

[Feature] Clustering and non-hierarchical state relationships

Excerpt from the original statecharts paper (emphasis mine):

The interrelationship between the states in all the statecharts presented thus far
is that of an AND/OR tree; actually an AND/XOR tree. However, there is absolutely
no deep reason for this, and statecharts need by no means be entirely tree-like.
While the human mind seems to perform well on tree-like hierarchical objects, we
definitely do not rule out clustering which is more complex. For example, Fig. 41
shows a situation in which state C has two parents. The reason for doing this might be conceptual similarities between the involved states, or merely the pragmatic
desire to economize when describing joint exits such as the two transitions appearing
in the figure. What is really happening is that states A and D are now related by
OR, not XOR. Of course, too much of this kind of overlapping will burden the
specification, with incomprehensibility possibly outweighing economy of description.
In such a case, one could resort to two copies of C.

Clustering example in a statechart

Proposed API:
🚧 Still being determined. Open to suggestions.

More technical details here: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.221.768&rep=rep1&type=pdf

Redux

Cool project! In the context of Redux, I suppose it'd enhance reducers somehow, would you like to share with us your implementation of the counter example for instance?

Transition to sub-state

Imagine I have this statechart:

const machine = Machine({
    initial: 'Init',
    states: {
        Init: {
            on: {
                FETCH_DATA_CLICKED: 'FetchingData',
            },
            initial: 'NoData',
            states: {
                ShowData: {},
                Error: {},
                NoData: {}
            }
        },
        FetchingData: {
            on: {
                FETCH_DATA_SUCCESS: 'ShowData',
                FETCH_DATA_FAILURE: 'Error',
                FETCH_DATA_CANCEL: 'NoData',
            },
            onEntry: 'FETCH_DATA_REQUEST',
        },
    }
});

When in FetchingData, is there a way to transition to a substate of Init?

Here's an example of what I'm trying to achieve:

screen shot 2017-12-18 at 23 56 24

[Feature] Internal events

Internal events are used in a few different ways in statecharts, e.g., for activities:

In order to enable statecharts to control activities too, we need two special kinds of actions to start and stop activities. Accordingly, with each activity X we associate two special new actions, start(X) and stop(X), and a new condition active(X), all with the obvious meanings.
(from Harel's paper)

The SCXML definition is as follows:

An event appearing in the internal event queue. Such events are either raised automatically by the platform or generated with the or elements.

These internal events are ones that would be necessary/useful:

  • start(activity)
  • active(activity) (might be seldom used)
  • stop(activity)
  • enter(state)
  • in(state) (differs from enter(state), called on every transition similar to active(activity))
    • would have to be renamed to inside(state) since in is a reserved keyword
  • exit(state)

This would also enable "communication" between orthogonal states by triggering transitions like this (contrived example):

Machine({
  parallel: true,
  states: {
    A: {
      initial: 'one',
      states: {
        one: {
          on: {
            TRIGGER: 'two'
          }
        },
        two: {}
      }
    },
    B: {
      initial: 'one',
      states: {
        one: {
          on: {
            [enter('A.two')]: 'two',
          }
        },
        two: {}
      }
    }
  }
});

The behavior of the above would be:

  1. Initial state: { A: 'one', B: 'one' }
  2. Event: TRIGGER
  3. Transition: { A: 'two', B: 'one' }
  4. Microstep: raise event enter('A.two')
    4.1. inside('B.one') would also be raised, but isn't handled by any state
  5. Transition: { A: 'two', B: 'two' }

The final state would be:

machine.transition(machine.initialState, 'TRIGGER');
// State {
//   value: { A: 'two', B: 'two' },
//   // ...
// }

For implementation, we can use Symbol(...) in order to uniquely identify xstate-specific internal event types. In turn, xstate would provide event creator functions like enter(state) and exit(state) to represent these internal events.

Thoughts? Am I missing anything?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.