Major Changes
-
#5512
063416dThanks @davidkpiano! - RemovecreateStoreWithProducer. Use(ctx, ev) => produce(ctx, draft => …)increateStoreevent handlers instead. -
#5512
063416dThanks @davidkpiano! - Addedenq.triggerfor enqueueing store events from transitions.const store = createStore({ schemas: { events: { inc: z.object({}), incTwice: z.object({}) } }, context: { count: 0 }, on: { inc: (context) => ({ count: context.count + 1 }), incTwice: (context, _event, enq) => { enq.trigger.inc(); enq.trigger.inc(); return context; } } });
-
#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.
-
#5512
063416dThanks @davidkpiano! - Updatepersist(...)helpers to support async storage results.clearStorage(...)andflushStorage(...)may return a promise when the configured storage is async. -
#5512
063416dThanks @davidkpiano! - Remove deprecated Store APIs.The deprecated config-wrapping form of
undoRedo(...)was removed. Use the extension form instead:const store = createStore({ context: { count: 0 }, on: { inc: (context) => ({ count: context.count + 1 }) } }).with(undoRedo());
Computed atoms now receive only the previous value. Read other atoms directly with
.get():-const doubled = createAtom((read) => read(countAtom) * 2); +const doubled = createAtom(() => countAtom.get() * 2); -const accumulated = createAtom((read, prev) => read(countAtom) + (prev ?? 0)); +const accumulated = createAtom((prev) => countAtom.get() + (prev ?? 0));
-
#5512
063416dThanks @davidkpiano! - Add Standard Schema support to store configs andfromStore(...).Schemas can type context, accepted events, and emitted events without enabling runtime validation by default. To validate schema-declared values at runtime, use the new
validateSchemas()extension from@xstate/store/validate.import { createStore } from '@xstate/store'; import { validateSchemas } from '@xstate/store/validate'; import { z } from 'zod'; const store = createStore({ schemas: { context: z.object({ count: z.number() }), events: { increment: z.object({ by: z.number() }) } }, context: { count: 0 }, on: { increment: (context, event) => ({ count: context.count + event.by }) } }).with(validateSchemas());
Minor Changes
-
#5512
063416dThanks @davidkpiano! - Pass anAbortSignaltocreateAsyncAtom(...)getters and ignore stale async results after recomputation.const user = createAsyncAtom(async ({ signal }) => { const response = await fetch('/user', { signal }); return response.json(); });
-
#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> ); }
-
#5512
063416dThanks @davidkpiano! - Add broadcast-aware storage helpers for persisted stores.Use
createBroadcastStorage(...)withpersist(...)andsubscribeToBroadcastStorage(...)to rehydrate a persisted store when another tab or window writes to the same storage key. -
#5512
063416dThanks @davidkpiano! - Addstore.canfor checking whether an event is allowed without updating the store.const store = createStore({ context: { count: 0 }, on: { increment: (context, event: { by: number }) => { if (context.count + event.by > 10) { return; } return { count: context.count + event.by }; } } }); store.can.increment({ by: 4 }); // true store.can.increment({ by: 11 }); // false
-
#5512
063416dThanks @davidkpiano! - AddcreateReducerAtom(...)for reducer-driven atoms.const count = createReducerAtom(0, (state, event: { type: 'inc' }) => { if (event.type === 'inc') { return state + 1; } return state; }); count.send({ type: 'inc' });