-
Notifications
You must be signed in to change notification settings - Fork 5
03 Higher Order Behavior
XSystem exports behavior that is useful for many different applications. Sometimes, this behavior can not be used directly and is rather used to extend behavior with common functionality. Similar to higher-order functions or higher-order components, higher-order behavior (HOB) receives a behavior and returns a new behavior.
We have already seen the usage of this concept for publishing events with the
HOB withPubSub
. This page includes
other HOBs that are provided as well.
This HOB removes the boilerplate of manually subscribing and unsubscribing
spawned behavior to publishers. In machines, the
fromActor
helper can be used to
subscribe the machine for the lifecycle of an invoked service automatically.
However, this is not available for pure behaviors. Instead, withSubscription
can be used to subscribe for the lifecycle of the spawned behavior to the
provided publisher.
import { spawnBehavior } from "xstate/lib/behaviors";
import { withSubscription, createEventBus, EventBus } from "xsystem";
type BusEvent = { type: "hello" } | { type: "world" };
function createCustomBehavior(bus: EventBus<BusEvent>) {
// Example of using `withSubscription` to subscribe automatically to
// published events of actors provided as factory arguments.
return withSubscription(
{
initialState: "Initial State",
transition: (state, e, ctx) => {
// Custom behavior logic...
return state;
},
},
bus,
["hello"]
);
}
const bus = spawnBehavior(createEventBus<BusEvent>());
const actor = spawnBehavior(createCustomBehavior(bus));
This feature is not fully implemented because it is currently not possible to define "onStop" behavior. Therefore, an actor is currently never unsubscribed when stopped. See the relevant issue for more information.
This HOB provides support for undo
and redo
events. The functionality is
based on state snapshots and not event sourcing, similar to the corresponding
mobx-state-tree middleware.
Sending an undo
or redo
event without previous/future snapshots keeps the
current state. Otherwise, undo
transitions to the previous state and redo
transitions to the next future state.
When an event is send while the behavior is not on top of the history stack, the future state is discarded and the sent event is put on top of the reduced state.
import { Behavior } from "xstate";
import { spawnBehavior } from "xstate/lib/behaviors";
import { withHistory, WithHistory, redo, undo, is } from "xsystem";
type Increment = { type: "increment"; by?: number };
type Decrement = { type: "decrement"; by?: number };
type CounterEvent = Increment | Decrement;
function createCounter(): WithHistory<Behavior<CounterEvent, number>> {
return withHistory({
initialState: 0,
transition: (state, event) => {
if (is<Increment>("increment", event)) {
return state + (event.by ?? 1);
}
if (is<Decrement>("decrement", event)) {
return state - (event.by ?? 1);
}
return state;
},
});
}
const counter = spawnBehavior(createCounter()); // => 0
counter.send({ type: "increment" }); // => 1
counter.send({ type: "increment" }); // => 2
counter.send({ type: "increment" }); // => 3
counter.send(undo()); // => 2
counter.send(undo()); // => 1
counter.send(undo()); // => 0
counter.send(redo()); // => 1
counter.send(redo()); // => 2
counter.send({ type: "decrement" }); // => 1
counter.send(redo()); // => 1 (previous future has been discarded after the decrement)
counter.send(undo()); // => 2