Skip to content

Commit 6f3373c

Browse files
committed
fix(builders): _xstateTree property typed incorrectly
Passes through the selector/action/slots types correctly when building a machine with createXStateTreeMachine so that accessing properties on the _xstateTree property has the correct types instead of the defaults
1 parent c900ff6 commit 6f3373c

File tree

4 files changed

+86
-14
lines changed

4 files changed

+86
-14
lines changed

src/builders.spec.tsx

+48-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,60 @@
11
import { act, render, waitFor } from "@testing-library/react";
22
import { createMemoryHistory } from "history";
33
import React from "react";
4+
import { setup } from "xstate";
45

5-
import { buildRoutingMachine, viewToMachine } from "./builders";
6+
import {
7+
buildRoutingMachine,
8+
createXStateTreeMachine,
9+
viewToMachine,
10+
} from "./builders";
611
import { buildCreateRoute } from "./routing";
712
import { XstateTreeHistory } from "./types";
813
import { buildRootComponent } from "./xstateTree";
914

1015
describe("xstate-tree builders", () => {
16+
describe("createXStateTreeMachine", () => {
17+
it("passes through the selectors/actions/slots/view types correctly through to the _xstateTree property", () => {
18+
const machine = setup({
19+
types: {
20+
context: {} as { foo: string; bar: string },
21+
},
22+
}).createMachine({
23+
context: {
24+
foo: "foo",
25+
bar: "bar",
26+
},
27+
initial: "idle",
28+
states: {
29+
idle: {},
30+
},
31+
});
32+
33+
const xstateTreeMachine = createXStateTreeMachine(machine, {
34+
selectors({ ctx }) {
35+
return {
36+
foobar: ctx.foo + ctx.bar,
37+
};
38+
},
39+
View({ selectors }) {
40+
return <div>{selectors.foobar}</div>;
41+
},
42+
});
43+
44+
const View = xstateTreeMachine._xstateTree.View;
45+
46+
<View
47+
actions={{}}
48+
slots={{}}
49+
selectors={{
50+
foobar: "foobar",
51+
// @ts-expect-error - should not be able to access context properties
52+
foo: "expect-error",
53+
}}
54+
/>;
55+
});
56+
});
57+
1158
describe("viewToMachine", () => {
1259
it("takes a React view and wraps it in an xstate-tree machine that renders that view", async () => {
1360
const ViewMachine = viewToMachine(() => <div>hello world</div>);

src/builders.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import React from "react";
22
import {
33
setup,
44
type AnyStateMachine,
5-
type ContextFrom,
65
AnyStateNodeConfig,
76
createMachine,
7+
type ContextFrom,
88
} from "xstate";
99

1010
import { AnyRoute } from "./routing";
@@ -37,11 +37,16 @@ export function createXStateTreeMachine<
3737
>(
3838
machine: TMachine,
3939
options: V2BuilderMeta<TMachine, TSelectorsOutput, TActionsOutput, TSlots>
40-
): XstateTreeMachine<TMachine> {
40+
): XstateTreeMachine<TMachine, TSelectorsOutput, TActionsOutput, TSlots> {
4141
const selectors = options.selectors ?? (({ ctx }) => ctx);
4242
const actions = options.actions ?? (() => ({}));
4343

44-
const machineWithMeta = machine as unknown as XstateTreeMachine<TMachine>;
44+
const machineWithMeta = machine as unknown as XstateTreeMachine<
45+
TMachine,
46+
TSelectorsOutput,
47+
TActionsOutput,
48+
TSlots
49+
>;
4550
machineWithMeta._xstateTree = {
4651
selectors: selectors as any,
4752
actions: actions as any,

src/types.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,35 @@ export type MatchesFrom<T extends AnyStateMachine> = (
8888
/**
8989
* @internal
9090
*/
91-
export type XstateTreeMachineInjection<TMachine extends AnyStateMachine> = {
92-
_xstateTree: XstateTreeMachineStateSchemaV2<TMachine>;
91+
export type XstateTreeMachineInjection<
92+
TMachine extends AnyStateMachine,
93+
TSelectorsOutput = ContextFrom<TMachine>,
94+
TActionsOutput = Record<never, string>,
95+
TSlots extends readonly Slot[] = Slot[]
96+
> = {
97+
_xstateTree: XstateTreeMachineStateSchemaV2<
98+
TMachine,
99+
TSelectorsOutput,
100+
TActionsOutput,
101+
TSlots
102+
>;
93103
};
94104

95105
/**
96106
* @public
97107
*/
98-
export type XstateTreeMachine<TMachine extends AnyStateMachine> = TMachine &
99-
XstateTreeMachineInjection<TMachine>;
108+
export type XstateTreeMachine<
109+
TMachine extends AnyStateMachine,
110+
TSelectorsOutput = ContextFrom<TMachine>,
111+
TActionsOutput = Record<never, string>,
112+
TSlots extends readonly Slot[] = Slot[]
113+
> = TMachine &
114+
XstateTreeMachineInjection<
115+
TMachine,
116+
TSelectorsOutput,
117+
TActionsOutput,
118+
TSlots
119+
>;
100120

101121
/**
102122
* @public

xstate-tree.api.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export function buildRoutingMachine<TRoutes extends AnyRoute[]>(_routes: TRoutes
115115
export type CanHandleEvent<TMachine extends AnyStateMachine> = (e: EventFrom<TMachine>) => boolean;
116116

117117
// @public
118-
export function createXStateTreeMachine<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = []>(machine: TMachine, options: V2BuilderMeta<TMachine, TSelectorsOutput, TActionsOutput, TSlots>): XstateTreeMachine<TMachine>;
118+
export function createXStateTreeMachine<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = []>(machine: TMachine, options: V2BuilderMeta<TMachine, TSelectorsOutput, TActionsOutput, TSlots>): XstateTreeMachine<TMachine, TSelectorsOutput, TActionsOutput, TSlots>;
119119

120120
// @public
121121
export const genericSlotsTestingDummy: any;
@@ -383,13 +383,13 @@ export type XstateTreeHistory<T = unknown> = History_2<{
383383
// Warning: (ae-incompatible-release-tags) The symbol "XstateTreeMachine" is marked as @public, but its signature references "XstateTreeMachineInjection" which is marked as @internal
384384
//
385385
// @public (undocumented)
386-
export type XstateTreeMachine<TMachine extends AnyStateMachine> = TMachine & XstateTreeMachineInjection<TMachine>;
386+
export type XstateTreeMachine<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = Slot[]> = TMachine & XstateTreeMachineInjection<TMachine, TSelectorsOutput, TActionsOutput, TSlots>;
387387

388388
// Warning: (ae-internal-missing-underscore) The name "XstateTreeMachineInjection" should be prefixed with an underscore because the declaration is marked as @internal
389389
//
390390
// @internal (undocumented)
391-
export type XstateTreeMachineInjection<TMachine extends AnyStateMachine> = {
392-
_xstateTree: XstateTreeMachineStateSchemaV2<TMachine>;
391+
export type XstateTreeMachineInjection<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = Slot[]> = {
392+
_xstateTree: XstateTreeMachineStateSchemaV2<TMachine, TSelectorsOutput, TActionsOutput, TSlots>;
393393
};
394394

395395
// @public (undocumented)
@@ -400,8 +400,8 @@ export type XstateTreeMachineStateSchemaV2<TMachine extends AnyStateMachine, TSe
400400
// src/routing/createRoute/createRoute.ts:285:19 - (ae-forgotten-export) The symbol "MergeRouteTypes" needs to be exported by the entry point index.d.ts
401401
// src/routing/createRoute/createRoute.ts:285:19 - (ae-forgotten-export) The symbol "ResolveZodType" needs to be exported by the entry point index.d.ts
402402
// src/routing/createRoute/createRoute.ts:322:9 - (ae-forgotten-export) The symbol "RouteRedirect" needs to be exported by the entry point index.d.ts
403-
// src/types.ts:117:3 - (ae-incompatible-release-tags) The symbol "canHandleEvent" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal
404-
// src/types.ts:118:3 - (ae-incompatible-release-tags) The symbol "inState" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal
403+
// src/types.ts:137:3 - (ae-incompatible-release-tags) The symbol "canHandleEvent" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal
404+
// src/types.ts:138:3 - (ae-incompatible-release-tags) The symbol "inState" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal
405405

406406
// (No @packageDocumentation comment for this package)
407407

0 commit comments

Comments
 (0)