Skip to content

Commit

Permalink
feat(builders): expose basic meta info from xstate
Browse files Browse the repository at this point in the history
Exposes the xstate meta object for the current state to the selectors. xstate does not expose typing information for the meta states via typegen so this is typed as unknown
  • Loading branch information
UberMouse committed Oct 4, 2023
1 parent fce2212 commit 13d2213
Show file tree
Hide file tree
Showing 6 changed files with 516 additions and 474 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Each machine that forms the tree representing your UI has an associated set of s
To assist in making xstate-tree easy to use with TypeScript there is the `createXStateTreeMachine` function for typing selectors, actions and view arguments and stapling the resulting functions to the xstate machine

`createXStateTreeMachine` accepts the xstate machine as the first argument and takes an options argument with the following fields, it is important the fields are defined in this order or TypeScript will infer the wrong types:
* `selectors`, receives an object with `ctx`, `inState`, and `canHandleEvent` fields. `ctx` is the machines current context, `inState` is the xstate `state.matches` function to allow determining if the machine is in a given state, and `canHandleEvent` accepts an event object and returns whether the machine will do anything in response to that event in it's current state
* `selectors`, receives an object with `ctx`, `inState`, `canHandleEvent`, and `meta` fields. `ctx` is the machines current context, `inState` is the xstate `state.matches` function to allow determining if the machine is in a given state, and `canHandleEvent` accepts an event object and returns whether the machine will do anything in response to that event in it's current state. `meta` is the xstate `state.meta` object with all the per state meta flattened into an object
* `actions`, receives an object with `send` and `selectors` fields. `send` is the xstate `send` function bound to the machine, and `selectors` is the result of calling the selector function
* `view`, is a React component that receives `actions`, `selectors`, and `slots` as props. `actions` and `selectors` being the result of the action/selector functions and `slots` being an object with keys as the slot names and the values the slots React component

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export type Selectors<TMachine extends AnyStateMachine, TOut> = (args: {
ctx: ContextFrom<TMachine>;
canHandleEvent: CanHandleEvent<TMachine>;
inState: MatchesFrom<TMachine>;
meta?: unknown;
}) => TOut;

/**
Expand Down
11 changes: 11 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,14 @@ export function isNil<T>(
): value is null | undefined {
return value === null || value === undefined;
}

export function mergeMeta(meta: Record<string, any>) {
return Object.keys(meta).reduce((acc, key) => {
const value = meta[key];

// Assuming each meta value is an object
Object.assign(acc, value);

return acc;
}, {});
}
28 changes: 28 additions & 0 deletions src/xstateTree.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,34 @@ describe("xstate-tree", () => {
expect(childMachineHandler).toHaveBeenCalled();
});

it("passes the current states meta into the v2 selector functions", async () => {
const machine = createMachine({
id: "test-selectors-meta",
initial: "idle",
states: {
idle: {
meta: {
foo: "bar",
},
},
},
});

const XstateTreeMachine = createXStateTreeMachine(machine, {
selectors({ meta }) {
return { foo: (meta as any)?.foo };
},
View: ({ selectors }) => {
return <p>{selectors.foo}</p>;
},
});
const Root = buildRootComponent(XstateTreeMachine);

const { findByText } = render(<Root />);

expect(await findByText("bar")).toBeTruthy();
});

describe("getMultiSlotViewForChildren", () => {
it("memoizes correctly", () => {
const machine = createMachine({
Expand Down
3 changes: 2 additions & 1 deletion src/xstateTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { GetSlotNames, Slot } from "./slots";
import { GlobalEvents, AnyXstateTreeMachine, XstateTreeHistory } from "./types";
import { useConstant } from "./useConstant";
import { useService } from "./useService";
import { assertIsDefined, isLikelyPageLoad } from "./utils";
import { assertIsDefined, isLikelyPageLoad, mergeMeta } from "./utils";

export const emitter = new TinyEmitter();

Expand Down Expand Up @@ -249,6 +249,7 @@ export function XstateTreeView({ interpreter }: XStateTreeViewProps) {
ctx: current.context,
canHandleEvent,
inState,
meta: mergeMeta(current.meta),
});
break;
}
Expand Down
Loading

0 comments on commit 13d2213

Please sign in to comment.