JavaScript and TypeScript finite state machines and statecharts for the modern web

Overview


XState
JavaScript state machines and statecharts

npm version

JavaScript and TypeScript finite state machines and statecharts for the modern web.

๐Ÿ“– Read the documentation

๐Ÿ’™ Explore our catalogue of examples

โžก๏ธ Create state machines with the Stately Editor

๐Ÿ–ฅ Download our VS Code extension

๐Ÿ“‘ Adheres to the SCXML specification

๐Ÿ’ฌ Chat on the Stately Discord Community

Packages

Templates

Get started by forking one of these templates on CodeSandbox:

Super quick start

npm install xstate
import { createMachine, interpret } from 'xstate';

// Stateless machine definition
// machine.transition(...) is a pure function used by the interpreter.
const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
});

// Machine instance with internal state
const toggleService = interpret(toggleMachine)
  .onTransition((state) => console.log(state.value))
  .start();
// => 'inactive'

toggleService.send('TOGGLE');
// => 'active'

toggleService.send('TOGGLE');
// => 'inactive'

Promise example

๐Ÿ“‰ See the visualization on stately.ai/viz

See the code
import { createMachine, interpret, assign } from 'xstate';

const fetchMachine = createMachine({
  id: 'Dog API',
  initial: 'idle',
  context: {
    dog: null
  },
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      invoke: {
        id: 'fetchDog',
        src: (context, event) =>
          fetch('https://dog.ceo/api/breeds/image/random').then((data) =>
            data.json()
          ),
        onDone: {
          target: 'resolved',
          actions: assign({
            dog: (_, event) => event.data
          })
        },
        onError: 'rejected'
      },
      on: {
        CANCEL: 'idle'
      }
    },
    resolved: {
      type: 'final'
    },
    rejected: {
      on: {
        FETCH: 'loading'
      }
    }
  }
});

const dogService = interpret(fetchMachine)
  .onTransition((state) => console.log(state.value))
  .start();

dogService.send('FETCH');

Visualizer

Visualize, simulate, inspect, and share your statecharts in XState Viz

XState Viz

stately.ai/viz

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:

Finite State Machines

Finite states
Open in Stately Viz

import { createMachine } from 'xstate';

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

const currentState = 'green';

const nextState = lightMachine.transition(currentState, 'TIMER').value;

// => 'yellow'

Hierarchical (Nested) State Machines

Hierarchical states
Open in Stately Viz

import { createMachine } 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 currentState = 'yellow';

const nextState = lightMachine.transition(currentState, 'TIMER').value;
// => {
//   red: 'walk'
// }

lightMachine.transition('red.walk', 'PED_TIMER').value;
// => {
//   red: 'wait'
// }

Object notation for hierarchical states:

// ...
const waitState = lightMachine.transition({ red: 'walk' }, 'PED_TIMER').value;

// => { red: 'wait' }

lightMachine.transition(waitState, 'PED_TIMER').value;

// => { red: 'stop' }

lightMachine.transition({ red: 'stop' }, 'TIMER').value;

// => 'green'

Parallel State Machines

Parallel states
Open in Stately Viz

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 boldState = wordMachine.transition('bold.off', 'TOGGLE_BOLD').value;

// {
//   bold: 'on',
//   italics: 'off',
//   underline: 'off',
//   list: 'none'
// }

const nextState = wordMachine.transition(
  {
    bold: 'off',
    italics: 'off',
    underline: 'on',
    list: 'bullets'
  },
  'TOGGLE_ITALICS'
).value;

// {
//   bold: 'off',
//   italics: 'on',
//   underline: 'on',
//   list: 'bullets'
// }

History States

History state
Open in Stately Viz

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 checkState = paymentMachine.transition('method.cash', 'SWITCH_CHECK');

// => State {
//   value: { method: 'check' },
//   history: State { ... }
// }

const reviewState = paymentMachine.transition(checkState, 'NEXT');

// => State {
//   value: 'review',
//   history: State { ... }
// }

const previousState = paymentMachine.transition(reviewState, 'PREVIOUS').value;

// => { method: 'check' }

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.

Comments
  • invoke.onError transition not taken when error is thrown

    invoke.onError transition not taken when error is thrown

    Description

    When using invoke with an onError transition I expected throwing an error (or rejecting a promise) from the invoked thing to trigger the onError transition.

    Expected Result

    onError gets invoked, no error is reported to the console, and I decide what to do with the thrown error.

    Actual Result

    Console shows a red error and the onError transition is never taken.

    Reproduction

    https://codesandbox.io/s/xstate-child-machine-errors-h5csv

    Additional context

    I asked this question in gitter and @davidkpiano suggested that this should work correctly. @amitnovick had a useful workaround where you can sendParent(error(<invoked-id>, data)) which works but feels very clunky and requires child machines to know the id they were invoked with which is problematic.

    bug 
    opened by tivac 37
  • [Feature Request?] Context store for easily sharing data through machine hierarchy

    [Feature Request?] Context store for easily sharing data through machine hierarchy

    I'm working on a pretty complex app with many invoked machines. After a while, it gets kind of hard to keep track of where you've properly passed down context from a parent machine into an invoked one. As the app has gotten more complex, tracking down these errors has become more of a slog.

    As an example, let say I have an ecommerce app, and I need to keep my Order up to date. So I obviously have a browse machine, a cart machine, a checkout machine, a credit card checkout machine, a paypal checkout machine, etc etc. Not only do each one of those need to keep track of the Order, but each one of them can make changes to it.

    This was a problem for React a while ago, where you'd have to pass down a single prop way down the structure, and it got unwieldy fast. Redux was one solution, but also there are React Contexts which are more lightweight.

    I'm proposing something like this:

    const BMachine = Machine({
      /* ... */
      states: {
        c: {
          onEntry: 'changeThing'
        }
      }
    }, {
      actions: {
        changeThing: assignStore({
          thing: (ctx, evt, store) => store.thing + ' is the best'
        })
      }
    });
    
    Machine({
      /* ... */
      states: {
        a: {
          invoke: {
            src: 'b',
            shareStore: true
          }
      }.
      {
        services: {
            b: BMachine
        }
      }
    }).withStore({ thing: 'my value' });
    

    It's kind of a half baked idea, but I wanted to get thoughts on it. Maybe there's already an implementation of something like this that I missed, or maybe it's already in the pipeline, or maybe it's completely against the principles of the project. Figured I'd put it out there regardless.

    Looking at it now, I guess I could just make my own store and inject it into my actions and what not. I've actually seen implementations of things like that. I just thought it might be good to add it to the official project. Let me know what y'all think.

    enhancement 
    opened by jonlaing 34
  • useService and side-effects

    useService and side-effects

    In the TodoMVC example, Todo.jsx file we have the following code:

    useEffect(() => {
      todoRef.execute(state, {
        focusInput() {
          inputRef.current && inputRef.current.select();
        }
      });
    }, [state, todoRef]);
    

    I didn't like that approach because all the state side-effects are going to be executed twice. Once by the interpreter, and again by the useEffect. You can add some console.logs to todoMachine to confirm that. For example, all sendParent() actions are gonna be executed twice.

    It's not a big issue, but it's more a workaround than a proper solution.

    I don't know how to fix that, but I would like to open that GitHub issue to start some discussion.

    enhancement 
    opened by hnordt 33
  • [question] Use promises as guards?

    [question] Use promises as guards?

    Is it possible to use a promise that returns true or false as a guard?

    I'm writing a xstate interpreter using the serverless framework. Machines are defined in the serverlesss config and get interpreted by a lambda function triggered by dynamodb stream events. Actions and services are modeled by lambda functions called by the interpreter, which works fine.

    StateMachineRedisCache:
      StateMachine:
        id: redis-cache-state-machines
        Source: DynamoDB
        SourceTable: ${self:custom.tableName}
        TableName: ${self:custom.tableName}
        initial: idle
        states:
          idle:
            on:
              MODIFY: cache
              INSERT: cache
          cache:
            invoke:
              src: CacheStateMachines
              onDone:
                target: cached
                actions: ['Log']
              onError:
                target: failed
          cached:
            type: final
          failed:
            type: final
        actions:
          Log: serverless-state-machine-dev-logGlobal
        services:
          CacheStateMachines:
            arn: 
              Fn::GetAtt: [ CacheStateMachineLambdaFunction, Arn ]
    

    I would like to implement guard logic using lambda functions but I don't see a way to wait for the asynchronous call to complete before taking the transition.

    invalid question documentation 
    opened by C5H8NNaO4 30
  •  @xstate/vue not working with vue beta

    @xstate/vue not working with vue beta

    Description @xstate/vue is not working with vue beta. The module still use @vue/composition-api as a dependency. Should we drop it as vue has enter beta ?

    Actual Result when I use the example in the readme, I get an error like

    Uncaught SyntaxError: The requested module '/@modules/@xstate/vue/lib/index.js' does not provide an export named 'useMachine'
    

    Reproduction Create a new project with vue-cli or vite To use vue 3 (with composition api) with vue-cli you need to do : vue add vue-next Then just write the example in the readme file, you should get the error.

    I am pretty new to xstate and all the concepts behind it, so I am not sure how to start.

    Any ideas ? Thanks a lot !

    help wanted ๐Ÿ’š @xstate/vue 
    opened by mkhennoussi 29
  • useMachine Hook Improvement

    useMachine Hook Improvement

    https://xstate.js.org/docs/recipes/react.html#hooks

    If we change:

    return [current, service.send];
    

    To:

    return useMemo(() => [current, service.send], [current]);
    

    We unlock a nice provider feature:

    function ChatMachineProvider({ children }) {
      return (
        <ChatMachineContext.Provider value={useMachine(chatMachine)}>
          {children}
        </ChatMachineContext.Provider>
      )
    }
    
    documentation 
    opened by hnordt 28
  • [Feature] Spawning Actors

    [Feature] Spawning Actors

    โ„น๏ธ UPDATE

    Actors are now in version 4.6. Please see the documentation on actors ๐Ÿ“– to see the updated API (slightly different than this proposal).

    Bug or feature request?

    Feature

    Description:

    Statecharts describe the behavior of an individual service (or "Actor"). This means that if developers want to manage multiple invoked statechart services (Actors), they either have to:

    • Write external ad-hoc functionality for managing a system of Actors
    • Delegate handling to an external framework, such as React

    The React + XState TodoMVC example highlights this. Although the code is idiomatic React code, the child todoMachine Actors are contained in each <Todo> component, and the parent todosMachine Actor is not aware of the existence of those Actors. It only responds to events from those Actors and keeps track of their state through its context.

    This is fine, but not ideal for:

    • Modeling systems of Actors (e.g., one-to-many and hierarchical relationships between Actors)
    • Visualizing interconnected Actors (the system)
    • Portability - usage of a single "system of Actors" anywhere instead of having to piece together multiple Actors in differing ad-hoc framework-specific ways

    To remedy this (and better mirror actual Actor-model languages like Erlang and Akka), @johnyanarella and I propose this (tentative) API:

    (Feature) Potential implementation:

    • What the API would look like

    spawn(actorCreator)

    This is an action creator that instantiates an Actor (essentially the same as an invoked service ๐Ÿ“–) and keeps an internal reference of it in the service:

    import { Machine, spawn } from 'xstate';
    
    const createTodoMachine = (id, message) => Machine({ id, ...todoMachine })
      .withContext({ message });
    
    const todosMachine = Machine({
      id: 'todos',
      // ...
      on: {
        // Creates a new "todo" Actor with todoMachine behavior
        ADD_TODO: spawn((ctx, e) => createTodoMachine(e.id))
      }
    });
    

    onUpdate

    This is a shorthand for something like Events.ActorTransition (up for bikeshedding) or "xstate.actorTransition" which is an event dispatched from the spawned Actors whenever their state transitions:

    const todosMachine = Machine({
      id: 'todos',
      context: { todos: {} },
      // ...
      on: {
        // Creates a new "todo" Actor with todoMachine behavior
        ADD_TODO: spawn((ctx, e) => createTodoMachine(e.id, e.message))
      },
      onUpdate: {
        actions: assign({
          todos: (ctx, e) => ({ ...todos, [e.id]: e.state })
        })
      }
    });
    

    The event structure would look like:

    {
      type: Events.ActorTransition, // special XState event
      id: 'todo-123', // ID of todo
      state: State({ ... }), // State instance from transition
    }
    

    state.children

    This is a reference to all spawned child Actor instances. For example, if we spawned a "todo Actor", a state would look like:

    todosMachine.transition('active', {
      type: 'ADD_TODO',
      id: 'todo-1',
      message: 'hello'
    });
    // => State {
    //   value: 'active',
    //   context: {
    //     todos: {
    //       'todo-1': State {
    //         value: 'pending',
    //         context: { message: 'hello' }
    //         // ...
    //       }
    //     }
    //   },
    //   // ...
    //   children: Map {
    //     'todo-1': Interpreter { ... }
    //     // ...
    //   }
    // }
    

    JSON.stringify(...) will not display Interpreter instances.

    • If this is a breaking change No, not a breaking change.

    • If this is part of the existing SCXML specification This leverages existing parts of SCXML by:

    1. Invoking a proprietary "Supervisor" service for the lifetime of the machine
    2. spawn(...) is just an action object; i.e.:
    spawn('todo');
    // => {
    //   type: 'xstate.send',
    //   target: '__supervisor',
    //   event: {
    //     type: 'xstate.spawn',
    //     source: 'todo'
    //   }
    // }
    
    1. The "Supervisor" service will automatically subscribe (.onTransition(...)) to spawned machines, keep a reference of them, call sendParent(Events.ActorTransition, ...) on each change, and .stop() each individual child service when the parent service is stopped.
    enhancement ๐Ÿ’ฌ RFC 
    opened by davidkpiano 27
  • useMachine stops working after fast refresh

    useMachine stops working after fast refresh

    Description The useMachine hook works as expected in react native when the app loads for the first time, then after a "fast reload", the hook stops updating the state, send still works and the machine completes actions in the background, but the state is never updated.

    Expected Result When performing a "fast reload", the useMachine hook should still return the current state.

    Actual Result After "fast reload" the useMachine hook never updates even though the state machine is still working.

    Reproduction Should be fairly easy to reproduce using react native and performing a fast refresh.

    Additional context xstate: 4.7.8 xstate/react: 0.8.1 react: 16.9.0 react-native: 0.61.5

    bug โš›๏ธŽ @xstate/react 
    opened by Nohac 26
  • Compilation issues with Typescript & xstate 4.0.0-13 & 4.0.0-14

    Compilation issues with Typescript & xstate 4.0.0-13 & 4.0.0-14

    Hi David,

    Since version 4.0.0-13, I'm having the following compilation issues:

    node_modules/xstate/lib/actionTypes.d.ts(1,30): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(1,30): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(2,29): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(2,29): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(3,30): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(3,30): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(4,29): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(4,29): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(5,31): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(5,31): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(6,30): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(6,30): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(8,31): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(8,31): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(9,30): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(9,30): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(10,34): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(10,34): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(11,28): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(11,28): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(12,29): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(12,29): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    node_modules/xstate/lib/actionTypes.d.ts(13,31): error TS2304: Cannot find name 'ActionTypes'.
    node_modules/xstate/lib/actionTypes.d.ts(13,31): error TS1254: A 'const' initializer in an ambient context must be a string or numeric literal.
    

    Why isn't there any import of ActionTypes in actionTypes.d.ts?

    No idea for the TS1254 error.

    I wasn't able to reproduce in codesandox.io, there the compilation seems to work fine. If you need it, I can provide you with a sample project reproducing the issue.

    help wanted typescript 
    opened by laurentpierson 25
  • Support delayed events

    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

    enhancement 
    opened by mogsie 25
  • [@xstate/test] Next version low-level API

    [@xstate/test] Next version low-level API

    Summary of changes:

    Test Model

    import { createTestModel } from '@xstate/test';
    
    // Creating a test model from an existing machine
    const testModel = createTestModel(someMachine);
    
    // Provide assertions for each state
    const testModel = createTestModel(someMachine, {
      states: {
        loading: async (state) => {
          // expect some SUT to be in 'loading' state
        },
        success: async (state) => {
          // ...
        },
        '*': async (state) => {
          // Tests any unmatched state
        }
      }
    });
    
    // Provide cases and execution for events
    const testModel = createTestModel(someMachine, {
      // ...
      events: {
        'form.submit': {
          cases: [{ inputValue: 'invalid' }, { inputValue: 'valid' }],
          exec: async ({ state, event }) => {
            // execute event, or throw error if event can't be executed
          }
        },
        'options.click': {
          cases: (state) => state.context.possibleOptions.map(...), // dynamically generate events
          exec: async (step) => { ... }
        }
      }
    });
    

    Custom test model

    import { TestModel } from '@xstate/test';
    
    // creating a test model from any custom behavior
    const testModel = new TestModel({
      initialState: { ... },
      transition: (state, event) => { ... },
      getEvents: (state) => { ... }
    });
    

    Generating paths

    // Get shortest paths
    const paths = testModel.getShortestPaths();
    
    // Get simple paths
    const paths = testModel.getSimplePaths();
    
    // Get paths that reach specific state
    const paths = testModel.getShortestPathsTo(state => state.matches('success'));
    
    const paths = testModel.getSImplePathsTo(state => state.matches('success'));
    

    Custom plan generation

    // Provide a custom path generator (to be documented)
    const randomPathGenerator = (behavior, options) => {
      // ...
    
      // Generates an array of paths
      return [ ... ];
    }
    
    const paths = testModel.getPaths(randomPathGenerator);
    

    Testing plans

    const paths = testModel.getShortestPaths();
    
    // Test all plans
    await testModel.testPaths(paths);
    
    // Test one plan at a time
    for (const path of paths) {
      await testModel.testPath(path);
    }
    

    Prevent infinite traversal

    // Will throw error if traversal limit reached
    const paths = testModel.getShortestPaths({ traversalLimit: 100 });
    

    Todo:

    • [x] Add configure(...)
    • [x] Add coverage property to configure(...)
    • [x] Add planGenerator property to configure(...) #3302
    • [x] Add default for .getPlans() (use planGenerator as default) #3298
    • [x] Add default for .testPlans() (use planGenerator as default) #3298
    • [x] Add changeset for .getCoverage(...) and .testCoverage(...)
    • [ ] Add changeset for configure(...)
    • [ ] Add changeset for createTestModel() including options ({ states, events })
    • [ ] Add changeset for .getPlans() and .testPlans()
    • [ ] Add changeset for .testPlan()
    • [ ] Add changeset for .getShortestPlans() and .getShortestPlansTo()
    • [ ] Add changeset for .getSimplePlans() and .getSimplePlansTo()
    • [ ] Add changeset for .getPlanFromEvents()
    • [ ] Add changeset for .testPath()
    • [ ] Add changeset for { traversalLimit } property on .get*Plans()
    • [ ] Add changeset for custom plan generators
    • [ ] Add changeset for events options: { cases, exec }
    • [x] Decide whether to remove actions?
    • [x] Add wildcard for events testing (deferred)
    • [x] Remove beforePath and afterPath
    • [x] Deduplicate path generation by default #3304
    • [x] Prevent users passing invoke or after in machine configs #3303
    • [x] Add testSync methods #3297
    • [x] Refactor to paths only, not plans
    • [ ] Transition traversal and coverage by default (configurable)
    • [ ] @davidkpiano add JSDoc comment to getAdjancencyMap - https://github.com/statelyai/xstate/pull/3036#discussion_r874806807
    • [ ] Remove branded SerializedState and SerializedEvent types - https://github.com/statelyai/xstate/pull/3036#discussion_r874843683
    • [ ] Add comment to ValueMapAdjOptions - https://github.com/statelyai/xstate/pull/3036/files/3f06f9f97ee6e468b786d27ef04b4cdc0a4982c5#r874914838
    opened by davidkpiano 24
  • [Documentation] - Service Schema Typescript

    [Documentation] - Service Schema Typescript

    Added a note about how service schema accepts only type with data key. But while writing the service function, we have to return the type assigned to data key. This misinterpretation took me hours to realize.

    having this specifically mentioned in the documentation would help

    opened by JayeshVP24 3
  • Remove test action execution

    Remove test action execution

    This PR removes the ability to execute actions in the upcoming @xstate/test. Executing side-effects should only be done either within the event execution or asserting the state (if needed).

    @Andarist Not sure how to handle the alpha changeset for this one

    opened by davidkpiano 3
  • feat(test): add tag support

    feat(test): add tag support

    (re-creating #3629 since it was based on a PR branch and got auto-closed)

    Fixed the typing issue in that one by using an any, would love a better fix if anybody sees it.

    opened by tivac 2
  • [v5] Refactor actions

    [v5] Refactor actions

    This PR does a bunch of things:

    • Removes side-effects from getInitialState
    • Refactors actions so that their implementations/executions are within the dynamic action object instead of stateUtils.ts
    • Adds tests and fixes some tests
    • Deletes ExecutableAction.ts
    • Deletes exec.ts
    • Changes the start() function for behaviors to return a state and be the place where side-effects are executed (as suggested by @andarist)
    • Breaking: changes the type of function actions to be โ€™xstate.actionโ€™ instead of the function name
    • Refactors some tests to be more robust (avoid testing deep implementation details)
    • Somehow fixes React strict mode execution to call things the expected number of times (e.g. 2 instead of 4)
    opened by davidkpiano 3
  • Bug: 'spawn(machine)' does not validate missing actions like 'useMachine(machine)' does

    Bug: 'spawn(machine)' does not validate missing actions like 'useMachine(machine)' does

    Description

    it appears that machines that are spawned lack the validation we get with useMachine. Looks like the spawn action is accepting machines that have a non 'never' values under tsConfig.missingImplementations

    Screen Shot 2022-12-23 at 10 51 36 AM

    Expected result

    it should throw a typescript error when invoking a non fully implemented machine

    Actual result

    it does not error

    Reproduction

    https://codesandbox.io/s/elastic-visvesvaraya-lgbnlj?file=/src/index.ts

    Additional context

    No response

    bug 
    opened by kirill578 0
  • [email protected](Dec 19, 2022)

  • [email protected](Dec 5, 2022)

    Minor Changes

    • #3607 f95180510 Thanks @davidkpiano! - The createModel(...) function is now marked as deprecated, as it will be removed in XState version 5. It is recommended to use Typegen instead.

    Patch Changes

    • #3677 a2ecf97ca Thanks @Andarist! - Fixed an issue with targeted ancestors not being correctly exited when reented during external transitions.

    • #3623 163c25562 Thanks @arromeo! - Fixed an issue with external transitions targeting ancestor states. In such a case, entry actions were incorrectly called on the states between the source state and the target state for states that were not reentered within this transition.

    • #3677 a2ecf97ca Thanks @Andarist! - Fixed an issue with the active descendants of the targeted ancestor not being correctly reentered during external transitions.

    • #3545 b9995f0 Thanks @with-heart! - Updated pure action types to allow action type strings to be returned in the array.

      const machine = createMachine(
        {
          entry: ['doStuff']
        },
        {
          actions: {
            doStuff: pure(() => ['someAction']),
            someAction: () => console.log('executed by doStuff')
          }
        }
      );
      

      Returning action type strings were already handled by xstate and the types now correctly reflect that.

    • #3666 5e0808eb4 Thanks @davidkpiano! - The warning for the predictableActionArguments setting has been improved to only warn if it is absent. You can disable the warning by setting predictableActionArguments: false. It's still recommended to set it to true though.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](Dec 5, 2022)

  • [email protected](Oct 29, 2022)

  • [email protected](Sep 9, 2022)

  • [email protected](Sep 1, 2022)

    Patch Changes

    • #3559 ddbc9bc5c Thanks @Andarist! - Fixed minor compatibility issues with TypeScript 4.8 in the codebase. This fixes the typechecking with TypeScript 4.8 in projects that don't use skipLibCheck: true.

    • #3563 e3c7a9caf Thanks @Andarist! - Fixed an issue with not executing actions in response to received batched events when using predictableActionArguments.

    • #3520 95a6a06d0 Thanks @Andarist! - Fixed a runtime crash when sending multiple events as an array to a service. It is not recommended to use this feature though as it will be removed in the next major version.

    Source code(tar.gz)
    Source code(zip)
  • [email protected](Aug 26, 2022)

  • [email protected](Aug 23, 2022)

    Patch Changes

    • #3540 121fad172 Thanks @Andarist! - Fixed an issue that caused invoked actors to be created before resolving assign actions from entry of the same state when using predictableActionArguments flag.

    • #3541 6c081ab87 Thanks @Andarist! - Fixed an issue with not being able to read the updated snapshot of a child when receiving and processing events from it and when using predictableActionArguments flag.

    Source code(tar.gz)
    Source code(zip)
  • [email protected](Aug 15, 2022)

  • [email protected](Aug 11, 2022)

  • [email protected](Aug 5, 2022)

    Minor Changes

    • #3289 c0a147e25 Thanks @Andarist! - A new predictableActionArguments feature flag has been added that allows you to opt into some fixed behaviors that will be the default in v5. With this flag:

      • XState will always call an action with the event directly responsible for the related transition,
      • you also automatically opt-into preserveActionOrder.

      Please be aware that you might not able to use state from the meta argument when using this flag.

    • #3126 37b751cb3 Thanks @Andarist! - All exit actions in the machine will now be correctly resolved and executed when a machine gets stopped or reaches its top-level final state. Previously, the actions were not correctly resolved and that was leading to runtime errors.

      To implement this fix in a reliable way, a new internal event has been introduced: { type: 'xstate.stop' } and when the machine stops its execution, all exit handlers of the current state (i.e. the active state nodes) will be called with that event. You should always assume that an exit handler might be called with that event.

    Patch Changes

    • #3178 6badd2ba3 Thanks @davidkpiano! - Added a dev-only error when forwardTo accidentally ends up trying to forward an event to an undefined actor. Such a situation indicates a logical error and risks an infinite loop.

    • #3453 368ed9b1c Thanks @pixtron! - Call the complete callback of the subscribed observer when an interpreter gets stopped.

    • #3422 e35493f59 Thanks @Andarist! - Fixed an issue with parallel regions not always being correctly reentered on external transitions of the containing parallel state targeting another region within that parallel state.

    • #3447 e93754d7a Thanks @davidkpiano! - The types for state.nextEvents are now properly typed to the actual event types of the machine. Original PR: #1115 (Thanks @alexreardon!)

    • #3424 88d540eb8 Thanks @Andarist! - Fixed an issue with targeted ancestors not being correctly reentered during external transitions.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](Aug 5, 2022)

  • @xstate/[email protected](May 30, 2022)

    Major Changes

    • #3036 Thanks @mattpocock, @davidkpiano! - Substantially simplified how paths and plans work in TestModel. Changed getShortestPlans and getSimplePlans to getShortestPaths and getSimplePaths. These functions now return an array of paths, instead of an array of plans which contain paths.

      Also added getPaths, which defaults to getShortestPaths. This can be passed a pathGenerator to customize how paths are generated.

    • #3036 Thanks @mattpocock! - Moved event cases out of events, and into their own attribute called eventCases:

      const model = createTestModel(machine, {
        eventCases: {
          CHOOSE_CURRENCY: [
            {
              currency: 'GBP'
            },
            {
              currency: 'USD'
            }
          ]
        }
      });
      
      model.getPaths().forEach((path) => {
        it(path.description, async () => {
          await path.test({
            events: {
              CHOOSE_CURRENCY: ({ event }) => {
                console.log(event.currency);
              }
            }
          });
        });
      });
      

      eventCases will also now always produce a new path, instead of only creating a path for the first case which matches.

    • #3036 Thanks @davidkpiano! - Removed .testCoverage(), and instead made getPlans, getShortestPlans and getSimplePlans cover all states and transitions enabled by event cases by default.
    • #3036 Thanks @davidkpiano! - Added validation on createTestModel to ensure that you don't include invalid machine configuration in your test machine. Invalid machine configs include invoke, after, and any actions with a delay.

      Added createTestMachine, which provides a slimmed-down API for creating machines which removes these types from the config type signature.

    • #3036 Thanks @davidkpiano! - getShortestPaths() and getPaths() will now traverse all transitions by default, not just all events.

      Take this machine:

      const machine = createTestMachine({
        initial: 'toggledOn',
        states: {
          toggledOn: {
            on: {
              TOGGLE: 'toggledOff'
            }
          },
          toggledOff: {
            on: {
              TOGGLE: 'toggledOn'
            }
          }
        }
      });
      

      In @xstate/test version 0.x, this would run this path by default:

      toggledOn -> TOGGLE -> toggledOff
      

      This is because it satisfies two conditions:

      1. Covers all states
      2. Covers all events

      But this a complete test - it doesn't test if going from toggledOff to toggledOn works.

      Now, we seek to cover all transitions by default. So the path would be:

      toggledOn -> TOGGLE -> toggledOff -> TOGGLE -> toggledOn
      
    • #3036 Thanks @mattpocock, @davidkpiano! - Moved events from createTestModel to path.test.

      Old:

      const model = createTestModel(machine, {
        events: {}
      });
      

      New:

      const paths = model.getPaths().forEach((path) => {
        path.test({
          events: {}
        });
      });
      

      This allows for easier usage of per-test mocks and per-test context.

    • #3036 Thanks @mattpocock, @davidkpiano! - Added states to path.test():

      const paths = model.getPaths().forEach((path) => {
        path.test({
          states: {
            myState: () => {},
            'myState.deep': () => {}
          }
        });
      });
      

      This allows you to define your tests outside of your machine, keeping the machine itself easy to read.

    Minor Changes

    Patch Changes

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](May 30, 2022)

    Major Changes

    • #3036 Thanks @davidkpiano! - Changed getSimplePaths to getSimplePlans, and getShortestPaths to getShortestPlans. Both of these functions can be passed a machine, and return StatePlan[].

      Added functions traverseSimplePlans, traverseShortestPlans,traverseShortestPlansFromTo, traverseSimplePlansTo and traverseSimplePlansFromTo, which can be passed a Behavior and return StatePlan[].

    Source code(tar.gz)
    Source code(zip)
  • [email protected](May 26, 2022)

    Major Changes

    • #1045 7f3b84816 Thanks @davidkpiano! - - The third argument of machine.transition(state, event) has been removed. The context should always be given as part of the state.

      • There is a new method: machine.microstep(state, event) which returns the resulting intermediate State object that represents a single microstep being taken when transitioning from state via the event. This is the State that does not take into account transient transitions nor raised events, and is useful for debugging.

      • The state.events property has been removed from the State object, and is replaced internally by state._internalQueue, which represents raised events to be processed in a macrostep loop. The state._internalQueue property should be considered internal (not used in normal development).

      • The state.historyValue property now more closely represents the original SCXML algorithm, and is a mapping of state node IDs to their historic descendent state nodes. This is used for resolving history states, and should be considered internal.

      • The stateNode.isTransient property is removed from StateNode.

      • The .initial property of a state node config object can now contain executable content (i.e., actions):

      // ...
      initial: {
        target: 'someTarget',
        actions: [/* initial actions */]
      }
      
      • Assign actions (via assign()) will now be executed "in order", rather than automatically prioritized. They will be evaluated after previously defined actions are evaluated, and actions that read from context will have those intermediate values applied, rather than the final resolved value of all assign() actions taken, which was the previous behavior.

      This shouldn't change the behavior for most state machines. To maintain the previous behavior, ensure that assign() actions are defined before any other actions.

    • #1669 969a2f4fc Thanks @davidkpiano! - An error will be thrown if an initial state key is not specified for compound state nodes. For example:

      const lightMachine = createMachine({
        id: 'light',
        initial: 'green',
        states: {
          green: {},
          yellow: {},
          red: {
            // Forgotten initial state:
            // initial: 'walk',
            states: {
              walk: {},
              wait: {}
            }
          }
        }
      });
      

      You will get the error:

      No initial state specified for state node "#light.red". Try adding { initial: "walk" } to the state config.
      
    • #2294 c0a6dcafa Thanks @davidkpiano! - The machine's context is now restricted to an object. This was the most common usage, but now the typings prevent context from being anything but an object:

      const machine = createMachine({
        // This will produce the TS error:
        // "Type 'string' is not assignable to type 'object | undefined'"
        context: 'some string'
      });
      

      If context is undefined, it will now default to an empty object {}:

      const machine = createMachine({
        // No context
      });
      
      machine.initialState.context;
      // => {}
      
    • #1260 172d6a7e1 Thanks @davidkpiano! - All generic types containing TContext and TEvent will now follow the same, consistent order:

      1. TContext
      2. TEvent
      3. ... All other generic types, including TStateSchema,TTypestate`, etc.
      -const service = interpret<SomeCtx, SomeSchema, SomeEvent>(someMachine);
      +const service = interpret<SomeCtx, SomeEvent, SomeSchema>(someMachine);
      
    • #1808 31bc73e05 Thanks @davidkpiano! - Renamed machine.withConfig(...) to machine.provide(...).

    • #878 e09efc720 Thanks @Andarist! - Removed third parameter (context) from Machine's transition method. If you want to transition with a particular context value you should create appropriate State using State.from. So instead of this - machine.transition('green', 'TIMER', { elapsed: 100 }), you should do this - machine.transition(State.from('green', { elapsed: 100 }), 'TIMER').

    • #1203 145539c4c Thanks @davidkpiano! - - The execute option for an interpreted service has been removed. If you don't want to execute actions, it's recommended that you don't hardcode implementation details into the base machine that will be interpreted, and extend the machine's options.actions instead. By default, the interpreter will execute all actions according to SCXML semantics (immediately upon transition).

      • Dev tools integration has been simplified, and Redux dev tools support is no longer the default. It can be included from xstate/devTools/redux:
      import { interpret } from 'xstate';
      import { createReduxDevTools } from 'xstate/devTools/redux';
      
      const service = interpret(someMachine, {
        devTools: createReduxDevTools({
          // Redux Dev Tools options
        })
      });
      

      By default, dev tools are attached to the global window.__xstate__ object:

      const service = interpret(someMachine, {
        devTools: true // attaches via window.__xstate__.register(service)
      });
      

      And creating your own custom dev tools adapter is a function that takes in the service:

      const myCustomDevTools = service => {
        console.log('Got a service!');
      
        service.subscribe(state => {
          // ...
        });
      };
      
      const service = interpret(someMachine, {
        devTools: myCustomDevTools
      });
      
      • These handlers have been removed, as they are redundant and can all be accomplished with .onTransition(...) and/or .subscribe(...):

        • service.onEvent()
        • service.onSend()
        • service.onChange()
      • The service.send(...) method no longer returns the next state. It is a void function (fire-and-forget).

      • The service.sender(...) method has been removed as redundant. Use service.send(...) instead.

    • #953 3de36bb24 Thanks @davidkpiano! - Support for getters as a transition target (instead of referencing state nodes by ID or relative key) has been removed.

      The Machine() and createMachine() factory functions no longer support passing in context as a third argument.

      The context property in the machine configuration no longer accepts a function for determining context (which was introduced in 4.7). This might change as the API becomes finalized.

      The activities property was removed from State objects, as activities are now part of invoke declarations.

      The state nodes will not show the machine's version on them - the version property is only available on the root machine node.

      The machine.withContext({...}) method now permits providing partial context, instead of the entire machine context.

    • #1443 9e10660ec Thanks @davidkpiano! - The in: ... property for transitions is removed and replaced with guards. It is recommended to use stateIn() and not(stateIn()) guard creators instead:

      + import { stateIn } from 'xstate/guards';
      
      // ...
      on: {
        SOME_EVENT: {
          target: 'somewhere',
      -   in: '#someState'
      +   cond: stateIn('#someState')
        }
      }
      // ...
      
    • #1456 8fcbddd51 Thanks @davidkpiano! - There is now support for higher-level guards, which are guards that can compose other guards:

      • and([guard1, guard2, /* ... */]) returns true if all guards evaluate to truthy, otherwise false
      • or([guard1, guard2, /* ... */]) returns true if any guard evaluates to truthy, otherwise false
      • not(guard1) returns true if a single guard evaluates to false, otherwise true
      import { and, or, not } from 'xstate/guards';
      
      const someMachine = createMachine({
        // ...
        on: {
          EVENT: {
            target: 'somewhere',
            guard: and([
              'stringGuard',
              or([{ type: 'anotherGuard' }, not(() => false)])
            ])
          }
        }
      });
      
    • #2824 515cdc9c1 Thanks @davidkpiano! - Actions and guards that follow eventless transitions will now receive the event that triggered the transition instead of a "null" event ({ type: '' }), which no longer exists:

      // ...
      states: {
        a: {
          on: {
            SOME_EVENT: 'b'
          }
        },
        b: {
          always: 'c'
        },
        c: {
          entry: [(_, event) => {
            // event.type is now "SOME_EVENT", not ""
          }]
        }
      }
      // ...
      
    • #1240 6043a1c28 Thanks @davidkpiano! - The in: '...' transition property can now be replaced with stateIn(...) and stateNotIn(...) guards, imported from xstate/guards:

      import {
        createMachine,
      + stateIn
      } from 'xstate/guards';
      
      const machine = createMachine({
        // ...
        on: {
          SOME_EVENT: {
            target: 'anotherState',
      -     in: '#someState',
      +     cond: stateIn('#someState')
          }
        }
      })
      

      The stateIn(...) and stateNotIn(...) guards also can be used the same way as state.matches(...):

      // ...
      SOME_EVENT: {
        target: 'anotherState',
        cond: stateNotIn({ red: 'stop' })
      }
      

      An error will now be thrown if the assign(...) action is executed when the context is undefined. Previously, there was only a warning.


      The SCXML event error.execution will be raised if assignment in an assign(...) action fails.


      Error events raised by the machine will be thrown if there are no error listeners registered on a service via service.onError(...).

    • #2824 6a6b2b869 Thanks @davidkpiano! - Eventless transitions must now be specified in the always: { ... } object and not in the on: { ... } object:

      someState: {
        on: {
          // Will no longer work
      -   '': { target: 'anotherState' }
        },
      + always: { target: 'anotherState' }
      }
      
    • #2484 0b49437b1 Thanks @davidkpiano! - Parameterized actions now require a params property:

      // ...
      entry: [
        {
          type: 'greet',
      -   message: 'Hello'
      +   params: { message: 'Hello' }
        }
      ]
      // ...
      
    • #987 0e24ea6d6 Thanks @davidkpiano! - The internal property will no longer have effect for transitions on atomic (leaf-node) state nodes. In SCXML, internal only applies to complex (compound and parallel) state nodes:

      Determines whether the source state is exited in transitions whose target state is a descendant of the source state. See 3.13 Selecting and Executing Transitions for details.

      // ...
      green: {
        on: {
          NOTHING: {
      -     target: 'green',
      -     internal: true,
            actions: doSomething
          }
        }
      }
      
    • #987 04e89f90f Thanks @davidkpiano! - The history resolution algorithm has been refactored to closely match the SCXML algorithm, which changes the shape of state.historyValue to map history state node IDs to their most recently resolved target state nodes.

    • #2882 0096d9f7a Thanks @davidkpiano! - The state.history property has been removed. This does not affect the machine "history" mechanism.

      Storing previous state should now be done explicitly:

      let previousState;
      
      const service = interpret(someMachine)
        .onTransition(state => {
          // previousState represents the last state here
      
          // ...
      
          // update the previous state at the end
          previousState = state;
        })
        .start();
      
    • #1456 8fcbddd51 Thanks @davidkpiano! - BREAKING: The cond property in transition config objects has been renamed to guard. This unifies terminology for guarded transitions and guard predicates (previously called "cond", or "conditional", predicates):

      someState: {
        on: {
          EVENT: {
            target: 'anotherState',
      -     cond: 'isValid'
      +     guard: 'isValid'
          }
        }
      }
      
    • #2060 b200e0e0b Thanks @davidkpiano! - The Machine() function has been removed. Use the createMachine() function instead.

      -import { Machine } from 'xstate';
      +import { createMachine } from 'xstate';
      
      -const machine = Machine({
      +const machine = createMachine({
        // ...
      });
      
    • #3148 7a68cbb61 Thanks @davidkpiano! - spawn is no longer importable from xstate. Instead you get it in assign like this:

      assign((ctx, ev, { spawn }) => {
        return {
          ...ctx,
          actorRef: spawn(promiseActor)
        };
      });
      

      In addition to that, you can now spawn actors defined in your implementations object, in the same way that you were already able to do that with invoke. To do that just reference the defined actor like this:

      spawn('promiseActor');
      
    • #2869 9437c3de9 Thanks @davidkpiano! - The service.batch(events) method is no longer available.

    • #2191 0038c7b1e Thanks @davidkpiano! - The StateSchema type has been removed from all generic type signatures.

    • #3148 7a68cbb61 Thanks @davidkpiano! - EmittedFrom type helper has been renamed to SnapshotFrom.

    • #1163 390eaaa52 Thanks @davidkpiano! - Breaking: The state.children property is now a mapping of invoked actor IDs to their ActorRef instances.

      Breaking: The way that you interface with invoked/spawned actors is now through ActorRef instances. An ActorRef is an opaque reference to an Actor, which should be never referenced directly.

      Breaking: The origin of an SCXML.Event is no longer a string, but an ActorRef instance.

    • #3148 7a68cbb61 Thanks @davidkpiano! - The services option passed as the second argument to createMachine(config, options) is renamed to actors. Each value in actors should be a function that takes in context and event and returns a [behavior](TODO: link) for an actor. The provided behavior creators are:

      • fromMachine
      • fromPromise
      • fromCallback
      • fromObservable
      • fromEventObservable
      import { createMachine } from 'xstate';
      +import { fromPromise } from 'xstate/actors';
      
      const machine = createMachine(
        {
          // ...
          invoke: {
            src: 'fetchFromAPI'
          }
        },
        {
      -   services: {
      +   actors: {
      -     fetchFromAPI: (context, event) => {
      +     fetchFromAPI: (context, event) => fromPromise(() => {
              // ... (return a promise)
            })
          }
        }
      );
      
    • #878 e09efc720 Thanks @Andarist! - Support for compound string state values has been dropped from Machine's transition method. It's no longer allowed to call transition like this - machine.transition('a.b', 'NEXT'), instead it's required to use "state value" representation like this - machine.transition({ a: 'b' }, 'NEXT').

    • #898 025a2d6a2 Thanks @davidkpiano! - - Breaking: activities removed (can be invoked)

      Since activities can be considered invoked services, they can be implemented as such. Activities are services that do not send any events back to the parent machine, nor do they receive any events, other than a "stop" signal when the parent changes to a state where the activity is no longer active. This is modeled the same way as a callback service is modeled.

    • #878 e09efc720 Thanks @Andarist! - Removed previously deprecated config properties: onEntry, onExit, parallel and forward.

    • #2876 c99bb43af Thanks @davidkpiano! - Typings for Typestate have been removed. The reason for this is that types for typestates needed to be manually specified, which is unsound because it is possible to specify impossible typestates; i.e., typings for a state's value and context that are impossible to achieve.

    • #2840 fc5ca7b7f Thanks @davidkpiano! - Invoked/spawned actors are no longer available on service.children - they can only be accessed from state.children.

    • #1811 5d16a7365 Thanks @davidkpiano! - Prefix wildcard event descriptors are now supported. These are event descriptors ending with ".*" which will match all events that start with the prefix (the partial event type before ".*"):

      // ...
      on: {
        'mouse.click': {/* ... */},
        // Matches events such as:
        // "pointer.move"
        // "pointer.move.out"
        // "pointer"
        'pointer.*': {/* ... */}
      }
      // ...
      

      Note: wildcards are only valid as the entire event type ("*") or at the end of an event type, preceded by a period (".*"):

      • โœ… "*"
      • โœ… "event.*"
      • โœ… "event.something.*"
      • โŒ ~"event.*.something"~
      • โŒ ~"event*"~
      • โŒ ~"event*.some*thing"~
      • โŒ ~"*.something"~
    • #1456 8fcbddd51 Thanks @davidkpiano! - The interface for guard objects has changed. Notably, all guard parameters should be placed in the params property of the guard object:

      Example taken from Custom Guards:

      -cond: {
      +guard: {
      - name: 'searchValid', // `name` property no longer used
        type: 'searchValid',
      - minQueryLength: 3
      + params: {
      +   minQueryLength: 3
      + }
      }
      
    • #1054 53a594e9a Thanks @Andarist! - Machine#transition no longer handles invalid state values such as values containing non-existent state regions. If you rehydrate your machines and change machine's schema then you should migrate your data accordingly on your own.

    • #1002 31a0d890f Thanks @Andarist! - Removed support for service.send(type, payload). We are using send API at multiple places and this was the only one supporting this shape of parameters. Additionally, it had not strict TS types and using it was unsafe (type-wise).

    Minor Changes

    • #3148 7a68cbb61 Thanks @davidkpiano! - onSnapshot is now available for invoke configs. You can specify a transition there to be taken when a snapshot of an invoked actor gets updated. It works similarly to onDone/onError.

    • #1041 b24e47b9e Thanks @Andarist! - Support for specifying states deep in the hierarchy has been added for the initial property. It's also now possible to specify multiple states as initial ones - so you can enter multiple descandants which have to be parallel to each other. Keep also in mind that you can only target descendant states with the initial property - it's not possible to target states from another regions.

      Those are now possible:

      {
        initial: '#some_id',
        initial: ['#some_id', '#another_id'],
        initial: { target: '#some_id' },
        initial: { target: ['#some_id', '#another_id'] },
      }
      
    • #1028 0c6cfee9a Thanks @Andarist! - Added support for expressions to cancel action.

    • #898 c9cda27cb Thanks @davidkpiano! - Added interop observable symbols to ActorRef so that actor refs are compatible with libraries like RxJS.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](May 26, 2022)

    Major Changes

    • #3148 7a68cbb61 Thanks @davidkpiano! - Removed getSnapshot parameter from composables. It is expected that the received actorRef has to have a getSnapshot method on it that can be used internally.

    Patch Changes

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](May 26, 2022)

    Major Changes

    • #3148 7a68cbb61 Thanks @davidkpiano! - Removed getSnapshot parameter from hooks. It is expected that the received actorRef has to have a getSnapshot method on it that can be used internally.

    Patch Changes

    Source code(tar.gz)
    Source code(zip)
  • [email protected](May 13, 2022)

  • [email protected](May 6, 2022)

    Minor Changes

    • #3234 ce376b388 Thanks @Andarist! - Added a StateValueFrom helper that can be used to extract valid state values from a machine. This might specifically be useful with typegen because typegenless state.matches accepts any anyway.

    Patch Changes

    • #3215 44c66e74f Thanks @tom-sherman! - Removing the timeout that's built in to waitFor is now supported by explicitly passing an Infinity value.

      Example usage:

      import { waitFor } from 'xstate/lib/waitFor';
      
      // This will
      const loggedInState = await waitFor(
        loginService,
        state => state.hasTag('loggedIn'),
        { timeout: Infinity }
      );
      

      This fixes a bug that causes waitFor to reject with an error immediately due to the behaviour of setTimeout.

    • #3230 780458c92 Thanks @Andarist! - Fixed an issue with typegen types not being able to provide events that had a union of strings as their type (such as { type: 'INC' | 'DEC'; value: number; }).

    • #3252 a94dfd467 Thanks @Andarist! - Fixed an issue with EventFrom not being able to extract events that had a union of strings as their type (such as { type: 'INC' | 'DEC'; value: number; }).

    • #3090 c4f73ca13 Thanks @Andarist! - Fixed an issue with action objects not receiving correct event types when used in the second argument to the createMachine.

    • #3238 3df6335ef Thanks @davidkpiano! - The typings for sendTo(...) have been fixed.

    • #3228 fe5f0e6c9 Thanks @Andarist! - Fixed an issue with inline functions in the config object used as transition actions not having their argument types inferred.

    • #3252 a94dfd467 Thanks @Andarist! - Fixed an issue with default TEvent ({ type: string }) not being correctly provided to inline transition actions.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](May 6, 2022)

    Major Changes

    • #3254 c0b787d2a Thanks @Andarist! - The major version of this package had to be bumped to allow integrating with the typegen. This package will now require TS version 4.0 or greater.

      When using hooks from @xstate/svelte it's recommended to skip providing explicit generics to them. Note that that generics list has changed since v1 and we now only accept a single generic, TMachine.

    Patch Changes

    • #3172 390a115cd Thanks @Andarist! - Fixed an issue with the internal interpreter created by useMachine being unsubscribed when its subscribers' count went to zero. The lifetime of this interpreter should be bound to the lifetime of the component that has created it.

    • #3209 8520e203b Thanks @schibrikov! - Added ESM build to fix some bundling issues, more information can be found here

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](May 6, 2022)

  • [email protected](Apr 8, 2022)

    Minor Changes

    • #3190 fbf5ca0ad Thanks @davidkpiano! - The waitFor(...) helper function, which asynchronously waits for an actor's emitted value to satisfy a predicate before a timeout, is now available.

      Example usage:

      import { waitFor } from 'xstate/lib/waitFor';
      
      // ...
      const loginService = interpret(loginMachine).start();
      
      const loggedInState = await waitFor(loginService, state =>
        state.hasTag('loggedIn')
      );
      
      loggedInState.hasTag('loggedIn'); // true
      
    • #3200 56c0a36 Thanks @Andarist! - Subscribing to a stopped interpreter will now always immediately emit its state and call a completion callback.

    Patch Changes

    • #3166 be4c5c74d Thanks @Andarist! - Fixed an issue with state.tags not having correct values when resolving micro transitions (taken in response to raised events). This was creating issues when checking tags in guards.

    • #3171 14f8b4785 Thanks @Andarist! - Fixed an issue with onDone on parallel states not being "called" correctly when a parallel state had a history state defined directly on it.

    • #2939 360e85462 Thanks @Andarist! - Fixed issues with not disposing some cached internal values when stopping interpreters, which could have led to issues when starting such an interpreter again.

    • #3153 b36ef9dda Thanks @Andarist! - Made type displays (like in the IDE tooltips etc) more readable by using a type interface for the internal ResolveTypegenMeta type.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](Apr 8, 2022)

  • @xstate/[email protected](Apr 8, 2022)

  • @xstate/[email protected](Apr 8, 2022)

    Major Changes

    • #2939 360e85462 Thanks @Andarist! - This package now accepts React 18 as a peer dep and the implementation has been rewritten to use use-sync-external-store package. This doesn't break compatibility with older versions of React since we are using the shim to keep compatibility with those older versions.

    • #2939 360e85462 Thanks @Andarist! - asEffect and asLayoutEffect action creators were removed. They were not fitting the React model that well and could lead to issues as their existence suggested that they are easy to use.

      To execute actions at those exact times you can always either just call your stuff directly from those effects or send events to the machine from those effects and execute explicit actions in response to said events.

    • #2939 360e85462 Thanks @Andarist! - The signatures of useMachine and useService integrating with @xstate/fsm were changed. They now only accept a single generic each (TMachine and TService respectively). This has been done to match their signatures with the related hooks that integrate with xstate itself.

    Patch Changes

    • #2939 360e85462 Thanks @Andarist! - In v2 we have changed signatures of useMachine and useInterpret. Instead of accepting a list of generics they now only support a single generic: TMachine. This change, erroneously, was only introduced to types targeting [email protected] but the types targeting previous TS releases were still using the older signatures. This has now been fixed and users of older TS versions should now be able to leverage typegen with @xstate/react.

    • #2939 360e85462 Thanks @Andarist! - useMachine for xstate now correctly rerenders with the initial state when the internal service is being restarted. This might happen during Fast Refresh and now you shouldn't be able to observe this stale state that didn't match the actual state of the service.

    • #2939 360e85462 Thanks @Andarist! - useMachine for @xstate/fsm now starts the service in an effect. This avoids side-effects in render and improves the compatibility with StrictMode.

    • #2939 360e85462 Thanks @Andarist! - Implementations given to useMachine targeting @xstate/fsm are now updated in a layout effect. This avoid some stale closure problems for actions that are executed in response to events sent from layout effects.

    • Updated dependencies [360e85462, 360e85462]:

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](Apr 8, 2022)

    Patch Changes

    • #3198 09e2130df Thanks @Andarist! - Fixed an issue that prevented some states from being sent correctly to the inspector when serializable values hold references to objects throwing on toJSON property access (like obj.toJSON). This property is accessed by the native algorithm before the value gets passed to the custom serializer. Because of a bug we couldn't correctly serialize such values even when a custom serializer was implemented that was meant to replace it in a custom way from within its parent's level.

    • #3199 f3d63147d Thanks @Andarist! - Fixed an issue that caused sending the same event multiple times to the inspector for restarted services.

    • #3076 34f3d9be7 Thanks @SimeonC! - Fixed an issue with "maximum call stack size exceeded" errors being thrown when registering a machine with a very deep object in its context despite using a serializer capable of replacing such an object.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](Apr 8, 2022)

    Major Changes

    • #2939 360e85462 Thanks @Andarist! - .start() (when called without any argument) now always starts from the initial state of the machine. This matches the behavior of XState itself.

    Patch Changes

    • #2939 360e85462 Thanks @Andarist! - A bunch of TypeScript helpers has been added: StateMachine.AnyMachine, StateMachine.AnyService, ContextFrom, EventFrom, StateFrom and ServiceFrom.
    Source code(tar.gz)
    Source code(zip)
  • [email protected](Mar 11, 2022)

    Patch Changes

    • #3131 d9a0bcfc9 Thanks @Andarist! - Fixed an issue with event type being inferred from too many places within createMachine call and possibly ending up as any/AnyEventObject for the entire machine.

    • #3133 4feef9d47 Thanks @fw6! - Fixed compatibility with esoteric Mini Program environment where global object was available but global.console wasn't.

    • #3140 502ffe91a Thanks @Andarist! - Fixed an issue with interpreters started using a persisted state not being "resolved" in full. This could cause some things, such as after transitions, not being executed correctly after starting an interpreter like this.

    • #3147 155539c85 Thanks @Andarist! - Fixed a TS inference issue causing some functions to infer the constraint type for the event type even though a StateMachine passed to the function was parametrized with a concrete type for the event. More information can be found here.

    • #3146 4cf89b5f9 Thanks @Andarist! - Fixed compatibility of Interpreter with older versions of TypeScript. This ensures that our interpreters can correctly be consumed by functions expecting ActorRef interface (like for example useSelector).

    • #3139 7b45fda9e Thanks @Andarist! - InterpreterFrom and ActorRefFrom types used on machines with typegen data should now correctly return types with final/resolved typegen data. The "final" type here means a type that already encodes the information that all required implementations have been provided. Before this change this wouldn't typecheck correctly:

      const machine = createMachine({
        // this encodes that we still expect `myAction` to be provided
        tsTypes: {} as Typegen0
      });
      const service: InterpreterFrom<typeof machine> = machine.withConfig({
        actions: {
          myAction: () => {}
        }
      });
      
    • #3097 c881c8ca9 Thanks @davidkpiano! - State that is persisted and restored from machine.resolveState(state) will now have the correct state.machine value, so that state.can(...) and other methods will work as expected. See #3096 for more details.

    Source code(tar.gz)
    Source code(zip)
  • @xstate/[email protected](Mar 11, 2022)

  • [email protected](Mar 4, 2022)

Owner
Stately
Visual software platform for app logic & workflows
Stately
๐Ÿ’พ๐Ÿ”—๐Ÿ–ฅ๏ธ Share, synchronize and persist state between multiple tabs with this plugin for Vuex. TypeScript types included.

vuex-multi-tab-state This Vuex plugin allows you to sync and share the status of your Vue application across multiple tabs or windows using the local

Gabriel Martรญn Blรกzquez 155 Nov 19, 2022
A project to demonstrate the use of Store and state, getters, mutations in Vuex and VueJS

Vuex State Sample ?? ?? A project to demonstrate the use of Store and state, getters, mutations in Vuex and VueJS. Create Vuex Store and use it to hol

Yufenyuy Veyeh Didier 0 Apr 13, 2020
State Management made eXtraordinarily simple and effective for Angular, React, and Vue

XSM - State Management made eXtraordinarily simple and effective for Angular, React, Vue, and Svelte. ?? Homepage Demos Angular React Svelte Vue Realw

Peter Lu 138 Sep 21, 2022
Sync and store vue state with browser URL params

vue-sync NOTE: Check out nuxt-url-sync to use it with SSR Sync Vue Component state with browser URL params Makes for easy bookmarking and sharing of v

Stefan Buhrmester 66 Nov 8, 2022
:rabbit2: A tiny, light and handy state management for vuejs 2, writing less verbose code.

revuejs ?? A tiny, light and handy state management for vuejs 2, writing less verbose code. Installation Install the pkg with npm: npm install revuejs

Pomy 24 May 5, 2021
A light and easy shared state management plugin for Vue

vue-shared vue-shared is a tiny (~150 lines) vue plugin for shared state management, that can be used as an alternative to Vuex. It adds a new vue opt

null 11 Jan 22, 2021
๐Ÿ“ฆ Fast, Simple, and Lightweight State Manager for Vue 3.0 built with composition API, inspired by Vuex.

v-bucket NPM STATUS: ?? Fast, Simple, and Lightweight State Management for Vue 3.0 built with composition API, inspired by Vuex. Table of Contents Mai

mehdi 42 Aug 10, 2022
Simple, unopinionated, lightweight and extensible state management for Vue 3

Simple, unopinionated, lightweight and extensible state management for Vue 3

Andrew Courtice 466 Dec 30, 2022
๐Ÿ’พ Persist and rehydrate your Vuex state between page reloads.

vuex-persistedstate Persist and rehydrate your Vuex state between page reloads. Install npm install --save vuex-persistedstate The UMD build is also a

Robin van der Vleuten 5.8k Jan 5, 2023
Vuex state persistance and synchronization between tabs/windows.

vuex-basement Vuex state persistance and synchronization between tabs/windows. Tested to work with Vue2. One Shortcomming (please read before use). Th

Rashad Saleh 65 Jun 29, 2022
Simple counter with Vue.js and Vuex as state management

vuex-counter Project setup npm install Compiles and hot-reloads for development npm run serve Compiles and minifies for production npm run build Li

Kevin Hamdajani 0 Dec 30, 2021
A state management library for React combined immutable, mutable and reactive mode

Welcome to bistate ?? Create the next immutable state tree by simply modifying the current tree bistate is a tiny package that allows you to work with

ๅทฅไธš่š 119 Jan 8, 2023
A state management library for react inspired by vue 3.0 reactivity api and immer

Welcome to costate ?? A state management library for react inspired by vue 3.0 reactivity api and immer costate is a tiny package that allows you to w

ๅทฅไธš่š 7 Mar 29, 2022
๐Ÿ—ƒ๏ธ Centralized State Management for Vue.js.

Vuex ?? HEADS UP! You're currently looking at Vuex 3 branch. If you're looking for Vuex 4, please check out 4.0 branch. Vuex is a state management pat

vuejs 27.9k Dec 30, 2022
Elm-inspired Application State Management for Vue.js.

VuElm It's a Vue state management inspired by Elm architecture. Concepts There are basically four elements on Elm architecture: Model, Actions, Update

Keuller Magalhรฃes 36 May 5, 2021
A very simple but powerful state management for vuejs projects.

Vuez A Simple but Powerful State Management for Vue.js projects. Vuez is a very simple but powerful state management library for Vue.js projects. Vuez

Mark Feng 42 Feb 18, 2022
The state manager โ˜„๏ธ

โ˜„๏ธ effector The state manager Table of Contents Introduction Effector follows five basic principles: Installation Documentation Packages Articles Comm

effector โ˜„๏ธ 4k Dec 28, 2022
State management system for Vue.js

Vue States Vue States is a state management system for Vue.js. Checkout the examples at https://github.com/JohannesLamberts/vue-states-examples. You m

sum.cumo GmbH 149 Nov 24, 2022
Vuex state synchronization between iframe/window

vuex-iframe-sync English | ไธญๆ–‡ Vuex state synchronization between iframe/window Your star is the greatest encouragement to me. โœจ Features: support ifra

Jiahui.Liang 101 Dec 17, 2022