-
Notifications
You must be signed in to change notification settings - Fork 5
04 Utilities
XSystem exports a few utilities that have proofed themself to be useful while developing the package.
Creates an EventCreator
for the given event type, which is an factory for
constructing new events of that type. An additional type
property is attached
to the returned EventCreator
function which equals the provided type and can
be used to avoid "magic strings".
A optional prepare callback as the second argument to createEvent
is used to
attach an additional payload to events that are constructed with the event
creator. The arguments received by the callback will be required as arguments
for the event creator.
Furthermore, a match
type predicate is attached to the returned
EventCreator
, which narrows given events to events with the same type.
import { createEvent } from "xsystem";
// constructing event creators.
const noData = createEvent("test.no_data");
const withData = createEvent("test.data", (id: string) => ({
id,
createdAt: new Date().toISOString(),
}));
//using the event creators
console.log(withData("123")); // => { type: "test.data", id: "123", createdAt: "2022-01-07T14:02:25.893Z" }
console.log(withData.type); // => "test.data"
someActor.send(noData()); // => sends { type: "test.no_data" } to someActor
noData.match({ type: "other" }); // => false
noData.match({ type: "test.no_data" }); // => true returned as matching type predicate
It is possible to use the type
property of event creators in machine
definitions. This has the advantage that it is easier to refactor the event
type. However, it prevents XState from correctly visualizing machines in
its VS Code extension
because definitions are parsed statically. It works fine in the
Stately Visualizer.
When we connect our user interface to actors, we have to handle UI events and convert and send them to corresponding actor.
Inside components, code such as the following is not uncommon:
import { useInterpret } from "@xstate/react";
import { createEvent } from "xsystem";
import { createActor } from "./actor";
// Would properly be defined somewhere else. Displayed here for clarity.
const event = createEvent("test.event", (data: string) => ({ data }));
function Component() {
const actor = useInterpret(createActor);
const handleData = (data: string) => actor.send(event(data));
return <OtherComponent onData={handleData}>Button</OtherComponent>;
}
Writing these event-handlers (handleData
) over and over again becomes tedious.
Especially, when they just proxy parameters the event-creator.
Therefore, each event-creator contains a createSendCall
function to create
such event-handlers for a given ActorRef
. This should help with reducing
boilerplate and should avoid a lot of work when many event-handlers have to be
created. The above code can be simplified to the following:
import { useInterpret } from "@xstate/react";
import { createEvent } from "xsystem";
import { createActor } from "./actor";
const event = createEvent("test.event", (data: string) => ({ data }));
function Component() {
const actor = useInterpret(createActor);
const handleData = event.createSendCall(actor);
return <OtherComponent onData={handleData}>Button</OtherComponent>;
}
Helper type that extracts the event shape from an EventCreator
. Extremely
useful to create types for events that a machine or behavior can receive. If the
provided generic does not extend EventCreator
, it falls back to the original
EventFrom
implementation in XState.
import { createEvent, EventFrom } from "xsystem";
const noData = createEvent("test.no_data");
const withData = createEvent("test.data", (id: string) => ({
id,
createdAt: new Date().toISOString(),
}));
type NoDataEvent = EventFrom<typeof noData>;
// { type: "test.no_data" }
type WithDataEvent = EventFrom<typeof withData>;
// { type: "test.data", id: string, createdAt: string }
A type predicate that narrows a given event to a specific event based on the
event type. It is most useful when defining a transition function for
Behavior
. If an EventCreator
exists for the event in question,
creator.match(event)
should be preferred because it is more ergonomic.
import { is } from "xsystem";
type Increment = { type: "increment"; by: number };
type Decrement = { type: "decrement"; by: number };
const event = {} as Increment | Decrement;
if (is<Increment>("increment", event)) {
// event is narrowed to an `Increment` event.
}
if (is<Decrement>("decrement", event)) {
// event is narrowed to an `Decrement` event.
}