Actor-based state management & orchestration for complex app logic.
MIT License
Bot releases are visible (Hide)
#4905 dbeafeb25
Thanks @davidkpiano! - You can now use a wildcard to listen for any emitted event from an actor:
actor.on('*', (emitted) => {
console.log(emitted); // Any emitted event
});
Published by github-actions[bot] 5 months ago
#4918 3323c85a6
Thanks @davidkpiano! - Types are now exported:
import type { SnapshotFromStore } from '@xstate/store';
// ...
Published by github-actions[bot] 5 months ago
#4896 7c6e2ea
Thanks @davidkpiano! - Test model path generation now has the option to allow duplicate paths by setting allowDuplicatePaths: true
:
const paths = model.getSimplePaths({
allowDuplicatePaths: true
});
// a
// a -> b
// a -> b -> c
// a -> d
// a -> d -> e
By default, allowDuplicatePaths
is set to false
:
const paths = model.getSimplePaths();
// a -> b -> c
// a -> d -> e
#4896 7c6e2ea
Thanks @davidkpiano! - The adjacencyMapToArray(…)
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 7c6e2ea
Thanks @davidkpiano! - The traversalLimit
option has been renamed to limit
:
model.getShortestPaths({
- traversalLimit: 100
+ limit: 100
});
#4233 3d96d0f95
Thanks @davidkpiano! - Remove getMachineShortestPaths
and getMachineSimplePaths
import {
- getMachineShortestPaths,
+ getShortestPaths,
- getMachineSimplePaths,
+ getSimplePaths
} from '@xstate/graph';
-const paths = getMachineShortestPaths(machine);
+const paths = getShortestPaths(machine);
-const paths = getMachineSimplePaths(machine);
+const paths = getSimplePaths(machine);
#4238 b4f12a517
Thanks @davidkpiano! - The steps in the paths returned from functions like getShortestPaths(...)
and getSimplePaths(...)
have the following changes:
step.event
property now represents the event
object that resulted in the transition to the step.state
, not the event that comes before the next step.path.steps
array now includes the target path.state
as the last step.
path.steps
always has at least one step.step
now has the { type: 'xstate.init' }
event#4896 7c6e2ea
Thanks @davidkpiano! - The createTestMachine(…)
function has been removed. Use a normal createMachine(…)
or setup(…).createMachine(…)
function instead to create machines for path generation.
#4896 7c6e2ea
Thanks @davidkpiano! - The filter
and stopCondition
option for path generation has been renamed to stopWhen
, 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 7c6e2ea
Thanks @davidkpiano! - Path generation now supports input
for 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 7c6e2ea
Thanks @davidkpiano! - The test model "sync" methods have been removed, including:
testModel.testPathSync(…)
testModel.testStateSync(…)
testPath.testSync(…)
The async
methods should always be used instead.
model.getShortestPaths().forEach(async (path) => {
- model.testPathSync(path, {
+ await model.testPath(path, {
states: { /* ... */ },
events: { /* ... */ },
});
})
5fb3c683d
Thanks @Andarist! - exports
field has been added to the package.json
manifest. 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.#4896 7c6e2ea
Thanks @davidkpiano! - The @xstate/graph
package now includes everything from @xstate/test
.
#4308 af032db12
Thanks @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
Published by github-actions[bot] 5 months ago
#4890 6d92b7770
Thanks @davidkpiano! - The context
type for createStoreWithProducer(producer, context, transitions)
will now be properly inferred.
const store = createStoreWithProducer(
produce,
{
count: 0
},
{
// ...
}
);
store.getSnapshot().context;
// BEFORE: StoreContext
// NOW: { count: number }
Published by github-actions[bot] 6 months ago
#4832 148d8fcef
Thanks @cevr! - fromPromise
now passes a signal into its creator function.
const logic = fromPromise(({ signal }) =>
fetch('https://api.example.com', { signal })
);
This will be called whenever the state transitions before the promise is resolved. This is useful for cancelling the promise if the state changes.
#4876 3f6a73b56
Thanks @davidkpiano! - XState will now warn when calling built-in actions like assign
, sendTo
, raise
, emit
, etc. directly inside of a custom action. See https://stately.ai/docs/actions#built-in-actions for more details.
const machine = createMachine({
entry: () => {
// Will warn:
// "Custom actions should not call \`assign()\` directly, as it is not imperative. See https://stately.ai/docs/actions#built-in-actions for more details."
assign({
// ...
});
}
});
Published by github-actions[bot] 6 months ago
#4863 0696adc21
Thanks @davidkpiano! - Meta objects for state nodes and transitions can now be specified in setup({ types: … })
:
const machine = setup({
types: {
meta: {} as {
layout: string;
}
}
}).createMachine({
initial: 'home',
states: {
home: {
meta: {
layout: 'full'
}
}
}
});
const actor = createActor(machine).start();
actor.getSnapshot().getMeta().home;
// => { layout: 'full' }
// if in "home" state
Published by github-actions[bot] 6 months ago
#4844 5aa6eb05c
Thanks @davidkpiano! - The useSelector(…)
hook from @xstate/react
is now compatible with stores from @xstate/store
.
import { createStore } from '@xstate/store';
import { useSelector } from '@xstate/react';
const store = createStore(
{
count: 0
},
{
inc: {
count: (context) => context.count + 1
}
}
);
function Counter() {
// Note that this `useSelector` is from `@xstate/react`,
// not `@xstate/store/react`
const count = useSelector(store, (state) => state.context.count);
return (
<div>
<button onClick={() => store.send({ type: 'inc' })}>{count}</button>
</div>
);
}
Published by github-actions[bot] 6 months ago
#4844 5aa6eb05c
Thanks @davidkpiano! - The useSelector(…)
hook from @xstate/react
is now compatible with stores from @xstate/store
.
import { createStore } from '@xstate/store';
import { useSelector } from '@xstate/react';
const store = createStore(
{
count: 0
},
{
inc: {
count: (context) => context.count + 1
}
}
);
function Counter() {
// Note that this `useSelector` is from `@xstate/react`,
// not `@xstate/store/react`
const count = useSelector(store, (state) => state.context.count);
return (
<div>
<button onClick={() => store.send({ type: 'inc' })}>{count}</button>
</div>
);
}
Published by github-actions[bot] 6 months ago
#4844 5aa6eb05c
Thanks @davidkpiano! - The useSelector(…)
hook from @xstate/react
is now compatible with stores from @xstate/store
.
import { createStore } from '@xstate/store';
import { useSelector } from '@xstate/react';
const store = createStore(
{
count: 0
},
{
inc: {
count: (context) => context.count + 1
}
}
);
function Counter() {
// Note that this `useSelector` is from `@xstate/react`,
// not `@xstate/store/react`
const count = useSelector(store, (state) => state.context.count);
return (
<div>
<button onClick={() => store.send({ type: 'inc' })}>{count}</button>
</div>
);
}
Published by github-actions[bot] 6 months ago
#4806 f4e0ec48c
Thanks @davidkpiano! - Inline actor logic is now permitted when named actors are present. Defining inline actors will no longer cause a TypeScript error:
const machine = setup({
actors: {
existingActor: fromPromise(async () => {
// ...
})
}
}).createMachine({
invoke: {
src: fromPromise(async () => {
// Inline actor
})
// ...
}
});
Published by github-actions[bot] 6 months ago
#4842 3a57f4c69
Thanks @davidkpiano! - Update README.md
#4839 4a22edb90
Thanks @davidkpiano! - Update JS docs
Published by github-actions[bot] 6 months ago
#4822 f7f1fbbf3
Thanks @davidkpiano! - The clock
and logger
specified in the options
object of createActor(logic, options)
will now propagate to all actors created within the same actor system.
import { setup, log, createActor } from 'xstate';
const childMachine = setup({
// ...
}).createMachine({
// ...
// Uses custom logger from root actor
entry: log('something')
});
const parentMachine = setup({
// ...
}).createMachine({
// ...
invoke: {
src: childMachine
}
});
const actor = createActor(parentMachine, {
logger: (...args) => {
// custom logger for args
}
});
actor.start();
Published by github-actions[bot] 6 months ago
#4752 8a32374e7
Thanks @davidkpiano! - Initial release of @xstate/store
import { createStore } from '@xstate/store';
const store = createStore(
// initial context
{ count: 0, greeting: 'hello' },
// transitions
{
inc: {
count: (context) => context.count + 1
},
updateBoth: {
count: () => 42,
greeting: 'hi'
}
}
);
store.send({
type: 'inc'
});
console.log(store.getSnapshot());
// Logs:
// {
// status: 'active',
// context: {
// count: 1,
// greeting: 'hello'
// }
// }
Published by github-actions[bot] 8 months ago
Published by github-actions[bot] 8 months ago
#4746 b570ba20d
Thanks @davidkpiano! - The new emit(…)
action creator emits events that can be received by listeners. Actors are now event emitters.
import { emit } from 'xstate';
const machine = createMachine({
// ...
on: {
something: {
actions: emit({
type: 'emitted',
some: 'data'
})
}
}
// ...
});
const actor = createActor(machine).start();
actor.on('emitted', (event) => {
console.log(event);
});
actor.send({ type: 'something' });
// logs:
// {
// type: 'emitted',
// some: 'data'
// }
#4777 4abeed9df
Thanks @Andarist! - Added support for params
to enqueueActions
Published by github-actions[bot] 8 months ago
Published by github-actions[bot] 8 months ago
Published by github-actions[bot] 8 months ago
f112081b9
Thanks @davidkpiano! - Fix send(…)
type for useActor(…)
Published by github-actions[bot] 8 months ago
Published by github-actions[bot] 8 months ago
#3727 5fb3c68
Thanks @Andarist! - exports
field has been added to the package.json
manifest. 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 1153b3f
Thanks @davidkpiano! - FSM-related functions have been removed.
#4748 d73ac8e48
Thanks @Andarist! - The createService(machine)
hook has been removed; use the useActorRef(logic)
hook instead.
#4748 d73ac8e48
Thanks @Andarist! - The fromActorRef(actorRef)
has been added. You can use it to get an accessor for reactive snapshot of any existing actorRef
.
#4748 d73ac8e48
Thanks @Andarist! - The useActor
hook accepts an actor logic
now and not an existing actorRef
. It's used to creating a new instance of an actor and it works just like useMachine
used to work (useMachine
is now just an alias of useActor
).