Skip to content

Releases: statelyai/xstate

v4.1.0

18 Nov 14:17
Compare
Choose a tag to compare
  • For browser builds, the global export was renamed to XState (previously: xstate).
  • The data property of the StateNode config is now an "Assigner" or "PropertyAssigner" instead of any.
  • The new State() constructor now takes an object with properties for each parameter, instead of positional arguments.
  • New static method: State.create({ ... }) which does the same as above.
  • The error(...) action creator now takes src as a second argument. For errors from an invoked service, the service ID will populate the src.
  • onError: ... transition property added to invoke config.
  • Numeric targets are now being coerced to strings, to resolve edge-case issues e.g., 3 !== '3' when selecting state transitions.
  • actionTypes.null is now actionTypes.nullEvent, which alleviates some autocomplete issues in VS Code, etc.
  • Instead of params and content, data will be used (polymorphic property - can take an "Assigner" or "PropertyAssigner")
  • Done data is now correctly passed to the 'done.state' event. #224
  • The process.env.NODE_ENV check will no longer produce errors in browser environments. #227
  • The Machine interface was renamed to StateMachine to prevent naming conflicts. #231
  • History state edge-cases are now resolved, and usage of the history property to indicate a history state is deprecated (use type: 'history' instead).

v4.0.1

18 Nov 14:05
Compare
Choose a tag to compare
  • TypeScript downgraded to 3.0.3 due to an existing bug in 3.1.3. See #213 for more information.

v4.0.0

30 Oct 00:48
Compare
Choose a tag to compare

State instance

  • 🆕 state.changed property indicates whether a state has changed from a previous state. A state is considered changed if:
    • its value is different from the previous state value
    • its value is unchanged but there are actions to be executed on the new state
  • 🆕 state.nextEvents property represents all next possible events from the current state.
  • 🆕 state.matches(parentStateValue) is equivalent to the matches() utility function, in that it determines if the current state matches the provided parent state value.
  • 💥 state.actions now returns an array of action objects instead of plain strings, with at least two properties:
    • type - the action type (string)
    • exec - the action implementation function (if it is defined).

Parallel states

  • 🔧 Flat nested parallel states are now representable in the state value, as empty objects ({}).
  • 💥 Using parallel: true is deprecated; use type: 'parallel' instead.

History States

  • 🆕 History states are explicitly defined with type: 'history' and history: 'parallel' or history: 'deep' (parallel by default). See https://xstate.js.org/docs/guides/history/ for more details.
  • 💥 The magic '$history' string is deprecated.

Final States

Machine

  • 🆕 machine.withConfig(...) lets you override configuration options in the original machine, as a new machine instance:
const customMachine = someMachine.withConfig({
  actions: {
	something: () => console.log('overridden something action')
  },
  guards: {
    someCondition: () => true
  }
});

Invoke

Interpreter

Assign and context

  • 🆕 Machines can be defined with a context, which is the extended state of the machine.
  • 🆕 The assign() action allows you to update the context declaratively. See https://xstate.js.org/docs/guides/context/ for details.
// increment a counter
{
  actions: assign({ counter: ctx => ctx.counter + 1 })
}

Delayed transitions and events

  • 🆕 Delayed transitions can be defined on the after: ... property of a state node config:
{
  after: {
	1000: 'yellow'
  }
}
  • 🆕 Delayed events can be defined as an option in send():
actions: send('ALERT', { delay: 1000 });

See https://xstate.js.org/docs/guides/delays/ for more details.

Actions and Events

  • 🆕 send(event, { to: ... }) allows you to specify the recipient machine for an event. See https://xstate.js.org/docs/guides/communication/#sending-events for more details.
  • 🆕 sendParent(event, options) similarly sends an event to the parent statechart that invoked the current child machine.
  • 🆕 log(expr, label) declaratively logs expressions given the current context and event.
  • 🆕 after(delay, id) creates an event for the given state node id that represents a delay of delay ms.
  • 🆕 done(id, data) represents that the parent state node with the given id is "done" - that is, all its final child state nodes have been reached. See https://xstate.js.org/docs/guides/final/ for more details.
  • 🆕 error(data) represents an execution error as an event.

Breaking changes

IDs are recommended on the root state node (machine):

const machine = Machine({
  id: 'light',
  initial: 'green',
  states: { /* ... */ }
});

This syntax will no longer work:

// ⚠️ won't work in v4
const machine = Machine({
  // ...
  states: {
    green: {
      on: {
        TIMER: {
          yellow: { actions: ['doSomething'] }
        }
      }
    }
  }
});

You now specify the transition as an object (or an array of objects) instead:

// ✅ will work in v4
const machine = Machine({
  // ...
  states: {
    green: {
      on: {
        TIMER: {
          target: 'yellow',
          actions: 'doSomething' // notice: array not necessary anymore!
        }
      }
    }
  }
});

Simple transitions as strings still work:

// ✅ still good
const machine = Machine({
  // ...
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    }
  }
});

When specifying types of state nodes, use type:

- parallel: true,
+ type: 'parallel'

- history: true,
+ type: 'history',
+ history: 'deep', // 'shallow' by default

And every property that takes an array now optionally takes an array if you have a single element:

{
  actions: ['doSomething']
  
  // You can do this instead:
  actions: 'doSomething'
}

Which is purely for convenience. That's about it!

v3.3.3

14 Jul 00:49
Compare
Choose a tag to compare
  • A substantial refactor to the history state algorithm fixed a few outstanding history issues: #133, #137
  • New method: machine.getStateNodeByPath(['foo', 'bar', 'baz']) (documentation pending)
  • Better resolution of deep parallel states to fix failing parallel state tests. #140

v3.3.2

09 Jun 04:48
Compare
Choose a tag to compare
  • More tests were added around the orthogonal (parallel) state value resolution logic, which was a regression from 3.2 but is now fixed.

v3.3.1

01 Jun 15:25
Compare
Choose a tag to compare
  • History states are only kept on the most recent state. This does not affect existing behavior, and prevents memory leaks. 23c84e2
  • Relative transitions are now all properly formatted, through all possible configurations. #117
  • Substates with the same key as the parent state will no longer create an incorrect transition. #118

v3.3.0

31 May 14:08
Compare
Choose a tag to compare

There's so many exciting improvements and features in this release. In general, the internal algorithms for determining next state, as well as actions, activities, events, and more were heavily refactored to adhere closer to the SCXML spec, as well as be easier to understand and maintain in the codebase. There's always room for improvement though, and we're always open to PRs!

Features and Improvements

  • Actions (onEntry, onExit, actions) can now be named functions, which should make authoring statecharts easier. #47 (📖 Docs)
    • They take in two arguments: extState (the external state passed into the transition(...) method) and event (the event object that caused the transition)
  • Guards (cond on transition configs) can now be strings, and referenced by the guards object in the machine config. #57 (docs coming soon!)
const enoughTimeElapsed = (extState, event) => {
  return event.emergency || extState.elapsed > 300;
};

const lightMachine = Machine({
  initial: 'green',
  states: {
	green: {
	  on: {
		TIMER: {
		  // string conditional
		  yellow: { cond: 'enoughTimeElapsed' }
		},
	  }
	},
	yellow: { /* ... */ }
  }
}, {
  // guard config
  guards: { enoughTimeElapsed }
});
  • Explicit history states - no more $history magic (this will still work and will be deprecated in 4.0). #86 (📖 Docs)
const historyMachine = Machine({
  initial: 'off',
  states: {
    fanOff: {
      on: {
        // transitions to history state
        POWER: 'fanOn.hist',
        HIGH_POWER: 'fanOn.highPowerHist'
      }
    },
    fanOn: {
      initial: 'first',
      states: {
        first: {
          on: { SWITCH: 'second' }
        },
        second: {
          on: { SWITCH: 'third' }
        },
        third: {},

        // shallow history state
        hist: {
          history: true
        },

        // shallow history state with default
        highPowerHist: {
          history: true,
          target: 'third'
        }
      },
      on: {
        POWER: 'fanOff'
      }
    }
  }
});
  • The getShortestPaths graph function now works with conditional guards when passed an external state. #100
  • Guard functions (cond) can now access the current state value, meaning you can use matchesState to determine if a transition should occur. #110
  • Lots of tests have been added (over 300 now!) and xstate is now even closer to full SCXML compatibility (for most use cases, it's already compatible).

Special thanks to @mogsie for his contributions!

v3.2.0

28 Apr 07:07
Compare
Choose a tag to compare

Plenty of new improvements and features in this release! 🎉

  • Support for metadata in state nodes, which are returned in the resulting State object. #45
{
  green: {
	on: { /* ... */ },
	data: {
	  name: 'Green Light'
	}
  }
}
  • Transient states (with eventless transitions) and conditional transition arrays supported. #43
  const myMachine = Machine({
    initial: 'G',
    parallel: false,
    states: {
      G: {
        on: { UPDATE_BUTTON_CLICKED: 'E' }
      },
      E: {
        on: {
          // eventless transition
          '': [
            { target: 'D', cond: ({ data }) => !data }, // no data returned
            { target: 'B', cond: ({ status }) => status === 'Y' },
            { target: 'C', cond: ({ status }) => status === 'X' },
            { target: 'F' } // default, or just the string 'F'
          ]
        }
      },
      D: {},
      B: {},
      C: {},
      F: {}
    }
  });
  • Partial support for SCXML conversion - full support coming 🔜
  • State nodes can now be targeted directly via ID.
    • A state node can have the id: 'foobar' prop, for example
    • A transition can target that ID via { target: '#foobar' } (or just '#foobar').
  • IDs can also be passed directly to machine.transition('#some-id', 'EVENT').
  • Partial support for internal (local) transitions, using .childState syntax. See #71
  • Multiple targets supported with array syntax, e.g., { target: ['foo.bar.one', 'foo.baz.quo'] }. #80
  • Just like conditions, now you can used named functions as actions instead of just strings!
  • matchesState will now return false if the parent state is more specific than the child state. #69

v3.1.1

04 Apr 03:14
Compare
Choose a tag to compare

Fixes

  • Updated "main" field in package.json to point to the correct distributed file (dist/xstate.js).

v3.0.1

08 Jan 15:18
Compare
Choose a tag to compare

This is the officially published v3 of xstate! 🎉

Some changes from V1 and V2:

  • machine.transition will always return a State object.
  • 🆕 onEntry, onExit actions for states, and actions for transition actions
  • 🆕 cond for conditional (guarded) transitions
  • 🆕 strict: true for strict mode (helpful in development)
  • Many more awesome things. For more information, read the docs.

If you're wondering why V3 and not V2:

  1. Versions are cheap.
  2. V2 was more of a sandbox version to test and dogfood new APIs. Those APIs have been solidified (thanks to help from the community) and have been released as V3, in case anyone (like myself) was using the undocumented V2 APIs.