XState for finite state machines
Last release 3 years ago
no release in 18 months
Release timing varies
gaps range from 2 weeks to 9 months
Some releases are documented
notes for 9 of 24 stable releases
Nothing withdrawn
no release was ever pulled
7 years old
28 releases · first in 2019
Release timeline
28 releases since 2019Releases
- 3.0.0-beta.311 Jul 2023pre-release
Nothing published for this version
- 3.0.0-beta.29 Apr 2023pre-release
Nothing published for this version
- 3.0.0-alpha.17 Apr 2023pre-release
Nothing published for this version
- 3.0.0-alpha.07 Jan 2023pre-release
- 2.1.021 Jun 2023
Release notes
Open source →Minor Changes
-
#5020
e974797b0Thanks @with-heart! - Added theEventFromStoreutility type which extracts the type of events from a store:import { createStore, type EventFromStore } from '@xstate/store'; const store = createStore( { count: 0 }, { add: (context, event: { addend: number }) => ({ count: context.count + event.addend }), multiply: (context, event: { multiplier: number }) => ({ count: context.count * event.multiplier }) } ); type StoreEvent = EventFromStore<typeof store>; // ^? { type: 'add'; addend: number } | { type: 'multiply'; multiplier: number }
EventFromStoreallows us to create our own utility types which operate on a store's event types.For example, we could create a type
EventByTypewhich extracts the specific type of store event whereTypematches the event'stypeproperty:import { type EventFromStore, type Store } from '@xstate/store'; /** * Extract the event where `Type` matches the event's `type` from the given * `Store`. */ type EventByType< TStore extends Store<any, any>, // creates a type-safe relationship between `Type` and the `type` keys of the // store's events Type extends EventFromStore<TStore>['type'] > = Extract<EventFromStore<TStore>, { type: Type }>;Here's how the type works with the
storewe defined in the first example:// we get autocomplete listing the store's event `type` values on the second // type parameter type AddEvent = EventByType<typeof store, 'add'>; // ^? { type: 'add'; addend: number } type MultiplyEvent = EventByType<typeof store, 'multiply'>; // ^? { type: 'multiply'; multiplier: number } // the second type parameter is type-safe, meaning we get a type error if the // value isn't a valid event `type` type DivideEvent = EventByType<typeof store, 'divide'>; // Type '"divide"' does not satisfy the constraint '"add" | "multiply"'.ts(2344)Building on that, we could create a type
EventInputByTypeto extract a specific event's "input" type (the event type without thetypeproperty):import { type EventFromStore, type Store } from '@xstate/store'; /** * Extract a specific store event's "input" type (the event type without the * `type` property). */ type EventInputByType< TStore extends Store<any, any>, Type extends EventFromStore<TStore>['type'] > = Omit<EventByType<TStore, Type>, 'type'>;And here's how
EventInputByTypeworks with our examplestore:type AddInput = EventInputByType<typeof store, 'add'>; // ^? { addend: number } type MultiplyInput = EventInputByType<typeof store, 'multiply'>; // ^? { multiplier: number } type DivideInput = EventInputByType<typeof store, 'divide'>; // Type '"divide"' does not satisfy the constraint '"add" | "multiply"'.ts(2344)Putting it all together, we can use
EventInputByTypeto create a type-safe transition function for each of our store's defined events:import { createStore, type EventFromStore, type Store } from '@xstate/store'; /** * Extract the event where `Type` matches the event's `type` from the given * `Store`. */ type EventByType< TStore extends Store<any, any>, Type extends EventFromStore<TStore>['type'] > = Extract<EventFromStore<TStore>, { type: Type }>; /** * Extract a specific store event's "input" type (the event type without the * `type` property). */ type EventInputByType< TStore extends Store<any, any>, Type extends EventFromStore<TStore>['type'] > = Omit<EventByType<TStore, Type>, 'type'>; const store = createStore( { count: 0 }, { add: (context, event: { addend: number }) => ({ count: context.count + event.addend }), multiply: (context, event: { multiplier: number }) => ({ count: context.count * event.multiplier }) } ); const add = (input: EventInputByType<typeof store, 'add'>) => store.send({ type: 'add', addend: input.addend }); add({ addend: 1 }); // sends { type: 'add', addend: 1 } const multiply = (input: EventInputByType<typeof store, 'multiply'>) => store.send({ type: 'multiply', multiplier: input.multiplier }); multiply({ multiplier: 2 }); // sends { type: 'multiply', multiplier: 2 }Happy typing!
-
- 2.0.16 Jun 2023
Release notes
Open source →Patch Changes
- Updated dependencies [
25963966c394fc904dc9b701a420b6e204ebe7f7]:
- Updated dependencies [
- 2.0.08 Apr 2022
Release notes4 sources agree
Open source →Major Changes
-
#5512
063416dThanks @davidkpiano! - Modernize Store v4 package entrypoints.Use framework-specific packages such as
@xstate/store-reactand@xstate/store-solidinstead of@xstate/store/reactor@xstate/store/solid. The Store packages now publish ESM package entrypoints. -
#5512
063416dThanks @davidkpiano! - AddcreateStoreLogic(...)for reusable store definitions, and support creating stores from logic in framework hooks.const counterLogic = createStoreLogic({ context: (input: { initialCount: number }) => ({ count: input.initialCount }), on: { inc: (context) => ({ count: context.count + 1 }) } }); const store = useStore(counterLogic, { initialCount: 0 });If a store logic requires input, the input argument is also required:
useStore(counterLogic, { initialCount: 0 });Framework hooks also preserve schema-derived context, event, and emitted event types when creating stores from config objects.
Minor Changes
-
#5512
063416dThanks @davidkpiano! - Add reusable atom configs and framework atom-state helpers.createAtomConfig(...)creates an inert atom definition that can be instantiated with itscreateAtom(...)method or React/Preact/Vue/Solid'suseAtomState(...). These helpers return the current framework-native value and live atom instance, and also work with existing atom instances.const countConfig = createAtomConfig((input: { initialCount: number }) => { return input.initialCount; }); function Counter() { const [count, countAtom] = useAtomState(countConfig, { initialCount: 0 }); return ( <button onClick={() => countAtom.set((count) => count + 1)}> {count} </button> ); }
Patch Changes
Additional notes2 sources agree
Open source →Major Changes
-
#5512
063416dThanks @davidkpiano! - Modernize Store v4 package entrypoints.Use framework-specific packages such as
@xstate/store-reactand@xstate/store-solidinstead of@xstate/store/reactor@xstate/store/solid. The Store packages now publish ESM package entrypoints. -
#5512
063416dThanks @davidkpiano! - AddcreateStoreLogic(...)for reusable store definitions, and support creating stores from logic in framework hooks.const counterLogic = createStoreLogic({ context: (input: { initialCount: number }) => ({ count: input.initialCount }), on: { inc: (context) => ({ count: context.count + 1 }) } }); const store = useStore(counterLogic, { initialCount: 0 });If a store logic requires input, the input argument is also required:
useStore(counterLogic, { initialCount: 0 });Framework hooks also preserve schema-derived context, event, and emitted event types when creating stores from config objects.
Patch Changes
Additional notes
Open source →Patch Changes
- Updated dependencies [
e07a7cd8462473188a0fb646a965e61be1ce6ae3]:
Additional notes
Open source →Major Changes
- #5000
eeadb7121Thanks @TkDodo! - - Replaceuse-sync-external-store/shimwithuseSyncExternalStorefrom React.- Do not memoize
getSnapshotinuseSyncExternalStore. - Implement
getServerSnapshotinuseSyncExternalStore. - Expect
storeto always be defined inuseSelector - Update React types to v18 and testing library to v16.
- Do not memoize
Additional notes
Open source →Major Changes
-
#4896
7c6e2eaThanks @davidkpiano! - Test model path generation now has the option to allow duplicate paths by settingallowDuplicatePaths: true:const paths = model.getSimplePaths({ allowDuplicatePaths: true }); // a // a -> b // a -> b -> c // a -> d // a -> d -> eBy default,
allowDuplicatePathsis set tofalse:const paths = model.getSimplePaths(); // a -> b -> c // a -> d -> e -
#4896
7c6e2eaThanks @davidkpiano! - TheadjacencyMapToArray(…)helper function has been introduced, which converts an adjacency map to an array of{ state, event, nextState }objects.import { getAdjacencyMap, adjacencyMapToArray } from '@xstate/graph'; const machine = createMachine({ initial: 'green', states: { green: { on: { TIMER: 'yellow' } }, yellow: { on: { TIMER: 'red' } }, red: { on: { TIMER: 'green' } } } }); const arr = adjacencyMapToArray(getAdjacencyMap(machine)); // [ // { // "state": {value: "green", ... }, // "event": { type: "TIMER" }, // "nextState": { value: "yellow", ... } // }, // { // "state": {value: "yellow", ... }, // "event": { type: "TIMER" }, // "nextState": { value: "red", ... } // }, // { // "state": {value: "red", ... }, // "event": { type: "TIMER" }, // "nextState": { value: "green", ... } // }, // { // "state": {value: "green", ... }, // "event": { type: "TIMER" }, // "nextState": { value: "yellow", ... } // }, // ] -
#4896
7c6e2eaThanks @davidkpiano! - ThetraversalLimitoption has been renamed tolimit:model.getShortestPaths({ - traversalLimit: 100 + limit: 100 }); -
#4233
3d96d0f95Thanks @davidkpiano! - RemovegetMachineShortestPathsandgetMachineSimplePathsimport { - getMachineShortestPaths, + getShortestPaths, - getMachineSimplePaths, + getSimplePaths } from '@xstate/graph'; -const paths = getMachineShortestPaths(machine); +const paths = getShortestPaths(machine); -const paths = getMachineSimplePaths(machine); +const paths = getSimplePaths(machine); -
#4238
b4f12a517Thanks @davidkpiano! - The steps in the paths returned from functions likegetShortestPaths(...)andgetSimplePaths(...)have the following changes:- The
step.eventproperty now represents theeventobject that resulted in the transition to thestep.state, not the event that comes before the next step. - The
path.stepsarray now includes the targetpath.stateas the last step.- Note: this means that
path.stepsalways has at least one step.
- Note: this means that
- The first
stepnow has the{ type: 'xstate.init' }event
- The
-
#4896
7c6e2eaThanks @davidkpiano! - ThecreateTestMachine(…)function has been removed. Use a normalcreateMachine(…)orsetup(…).createMachine(…)function instead to create machines for path generation. -
#4896
7c6e2eaThanks @davidkpiano! - ThefilterandstopConditionoption for path generation has been renamed tostopWhen, which is used to stop path generation when a condition is met. This is a breaking change, but it is a more accurate name for the option.const shortestPaths = getShortestPaths(machine, { events: [{ type: 'INC' }], - filter: (state) => state.context.count < 5 - stopCondition: (state) => state.context.count < 5 + stopWhen: (state) => state.context.count === 5 }); -
#4896
7c6e2eaThanks @davidkpiano! - Path generation now supportsinputfor actor logic:const model = createTestModel( setup({ types: { input: {} as { name: string; }, context: {} as { name: string; } } }).createMachine({ context: (x) => ({ name: x.input.name }), initial: 'checking', states: { checking: { always: [ { guard: (x) => x.context.name.length > 3, target: 'longName' }, { target: 'shortName' } ] }, longName: {}, shortName: {} } }) ); const path1 = model.getShortestPaths({ input: { name: 'ed' } }); expect(path1[0].steps.map((s) => s.state.value)).toEqual(['shortName']); const path2 = model.getShortestPaths({ input: { name: 'edward' } }); expect(path2[0].steps.map((s) => s.state.value)).toEqual(['longName']); -
#4896
7c6e2eaThanks @davidkpiano! - The test model "sync" methods have been removed, including:testModel.testPathSync(…)testModel.testStateSync(…)testPath.testSync(…)
The
asyncmethods should always be used instead.model.getShortestPaths().forEach(async (path) => { - model.testPathSync(path, { + await model.testPath(path, { states: { /* ... */ }, events: { /* ... */ }, }); })
Minor Changes
- #3727
5fb3c683dThanks @Andarist! -exportsfield has been added to thepackage.jsonmanifest. It limits what files can be imported from a package - it's no longer possible to import from files that are not considered to be a part of the public API.
Patch Changes
-
#4896
7c6e2eaThanks @davidkpiano! - The@xstate/graphpackage now includes everything from@xstate/test. -
#4308
af032db12Thanks @davidkpiano! - Traversing state machines that have delayed transitions will now work as expected:const machine = createMachine({ initial: 'a', states: { a: { after: { 1000: 'b' } }, b: {} } }); const paths = getShortestPaths(machine); // works
-
- 1.6.522 Feb 2022
Nothing published for this version
- 1.6.427 Jan 2022
Nothing published for this version
- 1.6.330 Dec 2021
Nothing published for this version
- 1.6.23 Sept 2021
Nothing published for this version
- 1.6.117 May 2021
Nothing published for this version
- 1.6.05 Feb 2021
Nothing published for this version
- 1.5.226 Nov 2020
Nothing published for this version
- 1.5.127 Aug 2020
Nothing published for this version
- 1.5.017 Aug 2020
Nothing published for this version
- 1.4.015 Apr 2020
Nothing published for this version
- 1.3.024 Jan 2020
Nothing published for this version
- 1.2.07 Jan 2020
Nothing published for this version
- 1.1.030 Oct 2019
Release notes3 sources agree
Open source →Minor Changes
- #5452
9992398Thanks @davidkpiano! - Ensured thatcompareargument is a direct comparison function.
- #5452
- 1.0.329 Oct 2019
Release notes
Open source →Patch Changes
- Updated dependencies [
b453b2d72ba12d0fe46a995f9ccced8000fd0cc9]:
- Updated dependencies [
- 1.0.15 Oct 2019
Release notes6 sources agree
Open source →Patch Changes
d6498ebThanks @davidkpiano! - Added README.md
Additional notes
Open source →Patch Changes
- Updated dependencies [
bf6119a7310a878afbf4f5b01f5e24288f9a0f16]:
- 1.0.04 Oct 2019
Release notes
Open source →Patch Changes
- Updated dependencies [
8c4b70652acaef2702f32435362e4755679a516d]:
Additional notes
Open source →Major Changes
- #5441
6ba9538Thanks @davidkpiano! - Initial release for@xstate/store-solid.
Patch Changes
- Updated dependencies [
6ba9538]:- @xstate/[email protected]
Additional notes
Open source →Major Changes
- #5441
6ba9538Thanks @davidkpiano! - Initial release for@xstate/store-vue.
Patch Changes
- Updated dependencies [
6ba9538]:- @xstate/[email protected]
Additional notes
Open source →Major Changes
- #5441
6ba9538Thanks @davidkpiano! - Initial release for@xstate/store-svelte.
Patch Changes
- Updated dependencies [
6ba9538]:- @xstate/[email protected]
Additional notes
Open source →Major Changes
- #5441
6ba9538Thanks @davidkpiano! - Initial release for@xstate/store-react.
Patch Changes
- Updated dependencies [
6ba9538]:- @xstate/[email protected]
Additional notes
Open source →Major Changes
- #5441
6ba9538Thanks @davidkpiano! - Initial release for@xstate/store-preact.
Patch Changes
- Updated dependencies [
6ba9538]:- @xstate/[email protected]
Additional notes
Open source →Major Changes
- #5441
6ba9538Thanks @davidkpiano! - Initial release for@xstate/store-angular.
Patch Changes
- Updated dependencies [
6ba9538]:- @xstate/[email protected]
Additional notes
Open source →Major Changes
- #4921
f73366504Thanks @davidkpiano! - Release@xstate/storeversion 1.0
- Updated dependencies [
- 0.3.021 Sept 2019
Nothing published for this version
- 0.2.115 May 2019
Release notes
Open source →Patch Changes
-
#5055
ad38c35c37Thanks @SandroMaglione! - Updated types ofuseActor,useMachine, anduseActorRefto requireinputwhen defined insidetypes/input.Previously even when
inputwas defined insidetypes,useActor,useMachine, anduseActorRefwould not make the input required:const machine = setup({ types: { input: {} as { value: number } } }).createMachine({}); function App() { // Event if `input` is not defined, `useMachine` works at compile time, but risks crashing at runtime const _ = useMachine(machine); return <></>; }With this change the above code will show a type error, since
inputis now required:const machine = setup({ types: { input: {} as { value: number } } }).createMachine({}); function App() { const _ = useMachine(machine, { input: { value: 1 } // Now input is required at compile time! }); return <></>; }This avoids runtime errors when forgetting to pass
inputwhen defined insidetypes.
-
- 0.2.015 May 2019
Release notes
Open source →Minor Changes
-
#3727
5fb3c68Thanks @Andarist! -exportsfield has been added to thepackage.jsonmanifest. It limits what files can be imported from a package - it's no longer possible to import from files that are not considered to be a part of the public API. -
#4265
1153b3fThanks @davidkpiano! - FSM-related functions have been removed. -
#4748
d73ac8e48Thanks @Andarist! - ThecreateService(machine)hook has been removed; use theuseActorRef(logic)hook instead. -
#4748
d73ac8e48Thanks @Andarist! - ThefromActorRef(actorRef)has been added. You can use it to get an accessor for reactive snapshot of any existingactorRef. -
#4748
d73ac8e48Thanks @Andarist! - TheuseActorhook accepts an actorlogicnow and not an existingactorRef. It's used to creating a new instance of an actor and it works just likeuseMachineused to work (useMachineis now just an alias ofuseActor).
-
- 0.1.112 May 2019
Nothing published for this version
- 0.1.012 May 2019
Nothing published for this version