Releases: statelyai/xstate
[email protected]
Minor Changes
-
#5042
54c9d9e6a4
Thanks @boneskull! -waitFor()
now accepts a{signal: AbortSignal}
inWaitForOptions
-
#5006
1ab974547f
Thanks @davidkpiano! - The state value typings for setup state machine actors (setup({}).createMachine({ ... })
) have been improved to represent the actual expected state values.const machine = setup({}).createMachine({ initial: 'green', states: { green: {}, yellow: {}, red: { initial: 'walk', states: { walk: {}, wait: {}, stop: {} } }, emergency: { type: 'parallel', states: { main: { initial: 'blinking', states: { blinking: {} } }, cross: { initial: 'blinking', states: { blinking: {} } } } } } }); const actor = createActor(machine).start(); const stateValue = actor.getSnapshot().value; if (stateValue === 'green') { // ... } else if (stateValue === 'yellow') { // ... } else if ('red' in stateValue) { stateValue; // { // red: "walk" | "wait" | "stop"; // } } else { stateValue; // { // emergency: { // main: "blinking"; // cross: "blinking"; // }; // } }
Patch Changes
-
#5054
853f6daa0b
Thanks @davidkpiano! - TheCallbackLogicFunction
type (previouslyInvokeCallback
) is now exported. This is the callback function that you pass intofromCallback(callbackLogicFn)
to create an actor from a callback function.import { type CallbackLogicFunction } from 'xstate'; // ...
@xstate/[email protected]
Minor Changes
-
#5056
8c35da9a72
Thanks @steveadams! - You can now use the xstate/store package with SolidJS.Import
useSelector
from@xstate/store/solid
. Select the data you want viauseSelector(…)
and send events usingstore.send(eventObject)
:import { donutStore } from './donutStore.ts'; import { useSelector } from '@xstate/store/solid'; function DonutCounter() { const donutCount = useSelector(donutStore, (state) => state.context.donuts); return ( <div> <button onClick={() => donutStore.send({ type: 'addDonut' })}> Add donut ({donutCount()}) </button> </div> ); }
@xstate/[email protected]
Patch Changes
b740aafdb1
Thanks @davidkpiano! - Fixed some small issues from #5027 regarding XState types being imported
[email protected]
Patch Changes
- #5039
d6df8fb470
Thanks @Andarist! - Fixed an inference issue that preventedemit
used directly insetup
(or barecreateMachine
) to benefit fromtypes.emitted
types.
@xstate/[email protected]
Minor Changes
-
#5027
758a78711d
Thanks @davidkpiano! - You can now inspect XState stores using the.inspect(inspector)
method:import { someStore } from './someStore'; someStore.inspect((inspEv) => { console.log(inspEv); // logs "@xstate.event" events and "@xstate.snapshot" events // whenever an event is sent to the store }); // The "@xstate.actor" event is immediately logged
[email protected]
Patch Changes
- #5034
7bed484c38
Thanks @davidkpiano! - FixEventFrom
andContextFrom
types
[email protected]
Patch Changes
-
#5029
88bd87ab41
Thanks @davidkpiano! - RevertActorRefFrom
change -
#5011
a275d274de
Thanks @davidkpiano! - There is a new type helper:ActorRefFromLogic<TLogic>
. This type is a stricter form ofActorRefFrom<TLogic>
that only accepts actor logic types. See #4997 for more details.
@xstate/[email protected]
Minor Changes
-
#5020
e974797b0
Thanks @with-heart! - Added theEventFromStore
utility 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 }
EventFromStore
allows us to create our own utility types which operate on a store's event types.For example, we could create a type
EventByType
which extracts the specific type of store event whereType
matches the event'stype
property: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
store
we 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
EventInputByType
to extract a specific event's "input" type (the event type without thetype
property):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
EventInputByType
works 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
EventInputByType
to 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!
[email protected]
Patch Changes
- #5009
51d4c4fc5
Thanks @davidkpiano! - The internal types forStateMachine<...>
have been improved so that all type params are required, to prevent errors when using the types. This fixes weird issues like #5008.
[email protected]
Minor Changes
-
#4979
a0e9ebcef
Thanks @davidkpiano! - State IDs are now strongly typed as keys ofsnapshot.getMeta()
for state machine actor snapshots.const machine = setup({ // ... }).createMachine({ id: 'root', initial: 'parentState', states: { parentState: { meta: {}, initial: 'childState', states: { childState: { meta: {} }, stateWithId: { id: 'state with id', meta: {} } } } } }); const actor = createActor(machine); const metaValues = actor.getSnapshot().getMeta(); // Auto-completed keys: metaValues.root; metaValues['root.parentState']; metaValues['root.parentState.childState']; metaValues['state with id']; // @ts-expect-error metaValues['root.parentState.stateWithId']; // @ts-expect-error metaValues['unknown state'];
Patch Changes
- #5002
9877d548b
Thanks @davidkpiano! - Fix an issue whereclearTimeout(undefined)
was sometimes being called, which can cause errors for some clock implementations. See #5001 for details.