Skip to content

Commit

Permalink
feat(xstate-tree): allow supplying input with buildRootComponent
Browse files Browse the repository at this point in the history
Updates the buildRootComponent function to allow specifying input to be passed to the actor when it's created

To enable proper typescript support this required changing the signature of buildRootComponent to accept a single object which contains the machine, optionally routing information, and input depending on whether the machine defines an input type
  • Loading branch information
UberMouse committed Feb 14, 2024
1 parent e3c427b commit 9a17ecc
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 91 deletions.
11 changes: 7 additions & 4 deletions examples/todomvc/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import { routes, history } from "./routes";

const appRoot = document.getElementById("root");
const root = createRoot(appRoot!);
const App = buildRootComponent(TodoApp, {
basePath: "/",
history,
routes,
const App = buildRootComponent({
machine: TodoApp,
routing: {
basePath: "/",
history,
routes,
},
});

root.render(<App />);
13 changes: 8 additions & 5 deletions src/builders.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe("xstate-tree builders", () => {
describe("viewToMachine", () => {
it("takes a React view and wraps it in an xstate-tree machine that renders that view", async () => {
const ViewMachine = viewToMachine(() => <div>hello world</div>);
const Root = buildRootComponent(ViewMachine);
const Root = buildRootComponent({ machine: ViewMachine });

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

Expand Down Expand Up @@ -41,10 +41,13 @@ describe("xstate-tree builders", () => {
GO_TO_BAR: BarMachine,
});

const Root = buildRootComponent(routingMachine, {
history: hist,
basePath: "/",
routes: [fooRoute, barRoute],
const Root = buildRootComponent({
machine: routingMachine,
routing: {
history: hist,
basePath: "/",
routes: [fooRoute, barRoute],
},
});

const { getByText } = render(<Root />);
Expand Down
20 changes: 10 additions & 10 deletions src/lazy.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("lazy", () => {
it("renders null by default when loading", () => {
const promiseFactory = () => new Promise<any>(() => void 0);
const lazyMachine = lazy(promiseFactory);
const Root = buildRootComponent(lazyMachine);
const Root = buildRootComponent({ machine: lazyMachine });

const { container, rerender } = render(<Root />);
rerender(<Root />);
Expand All @@ -32,7 +32,7 @@ describe("lazy", () => {
const lazyMachine = lazy(promiseFactory, {
Loader: () => <p>loading</p>,
});
const Root = buildRootComponent(lazyMachine);
const Root = buildRootComponent({ machine: lazyMachine });

const { container, rerender } = render(<Root />);
rerender(<Root />);
Expand Down Expand Up @@ -76,14 +76,14 @@ describe("lazy", () => {
});
const slots = [lazyMachineSlot];

const Root = buildRootComponent(
createXStateTreeMachine(rootMachine, {
const Root = buildRootComponent({
machine: createXStateTreeMachine(rootMachine, {
slots,
View({ slots }) {
return <slots.lazy />;
},
})
);
}),
});

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

Expand Down Expand Up @@ -144,14 +144,14 @@ describe("lazy", () => {
});
const slots = [lazyMachineSlot];

const Root = buildRootComponent(
createXStateTreeMachine(rootMachine, {
const Root = buildRootComponent({
machine: createXStateTreeMachine(rootMachine, {
slots,
View({ slots }) {
return <slots.lazy />;
},
})
);
}),
});

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

Expand Down
15 changes: 6 additions & 9 deletions src/routing/createRoute/createRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import { parse, ParsedQuery, stringify } from "query-string";
import * as Z from "zod";

import { XstateTreeHistory } from "../../types";
import { type IsEmptyObject } from "../../utils";
import {
type IsEmptyObject,
type MarkOptionalLikePropertiesOptional,
} from "../../utils";
import { joinRoutes } from "../joinRoutes";

type EmptyKeys<T> = keyof {
[K in keyof T as IsEmptyObject<T[K], true> extends true ? K : never]: T[K];
};
type MakeEmptyObjectPropertiesOptional<T> = Omit<T, EmptyKeys<T>> &
Partial<Pick<T, EmptyKeys<T>>>;

/**
* @public
*/
Expand Down Expand Up @@ -67,10 +64,10 @@ export type RouteArgumentFunctions<
? (args?: TArgs) => TReturn
: EmptyRouteArguments<TParams, TQuery> extends true
? (args?: Partial<TArgs>) => TReturn
: (args: MakeEmptyObjectPropertiesOptional<TArgs>) => TReturn;
: (args: MarkOptionalLikePropertiesOptional<TArgs>) => TReturn;

type RouteRedirect<TParams, TQuery, TMeta> = (
args: MakeEmptyObjectPropertiesOptional<{
args: MarkOptionalLikePropertiesOptional<{
params: TParams;
query: TQuery;
meta?: TMeta;
Expand Down
15 changes: 9 additions & 6 deletions src/test-app/AppMachine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,13 @@ export const BuiltAppMachine = createXStateTreeMachine(AppMachine, {
},
});

export const App = buildRootComponent(BuiltAppMachine, {
history,
basePath: "",
routes: [homeRoute, settingsRoute],
getPathName: () => "/",
getQueryString: () => "",
export const App = buildRootComponent({
machine: BuiltAppMachine,
routing: {
history,
basePath: "",
routes: [homeRoute, settingsRoute],
getPathName: () => "/",
getQueryString: () => "",
},
});
2 changes: 1 addition & 1 deletion src/test-app/tests/itWorksWithoutRouting.integration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const root = createXStateTreeMachine(rootMachine, {
},
});

const RootView = buildRootComponent(root);
const RootView = buildRootComponent({ machine: root });

describe("Environment without routing", () => {
it("still works without error", () => {
Expand Down
15 changes: 9 additions & 6 deletions src/test-app/tests/selectorsStaleCanHandleEvent.integration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import { OtherMachine } from "../OtherMachine";
import { settingsRoute } from "../routes";

const history = createMemoryHistory<any>();
const App = buildRootComponent(OtherMachine, {
history,
basePath: "",
routes: [settingsRoute],
getPathName: () => "/settings",
getQueryString: () => "",
const App = buildRootComponent({
machine: OtherMachine,
routing: {
history,
basePath: "",
routes: [settingsRoute],
getPathName: () => "/settings",
getQueryString: () => "",
},
});

describe("Selectors & canHandleEvent", () => {
Expand Down
8 changes: 4 additions & 4 deletions src/tests/actionsGetUpdatedSelectors.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ describe("actions accessing selectors", () => {
},
});

const Root = buildRootComponent(
createXStateTreeMachine(machine, {
const Root = buildRootComponent({
machine: createXStateTreeMachine(machine, {
actions({ selectors, send }) {
actionsCallCount++;
return {
Expand All @@ -43,8 +43,8 @@ describe("actions accessing selectors", () => {
<button onClick={actions.incrementCount}>{selectors.count}</button>
);
},
})
);
}),
});

it("gets the most up to date selectors value without re-creating the action functions", async () => {
const { getByRole, rerender } = render(<Root />);
Expand Down
10 changes: 5 additions & 5 deletions src/tests/asyncRouteRedirects.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ describe("async route redirects", () => {
},
});

const Root = buildRootComponent(
createXStateTreeMachine(machine, {
const Root = buildRootComponent({
machine: createXStateTreeMachine(machine, {
View: ({ selectors }) => <p>{selectors.bar}</p>,
}),
{
routing: {
basePath: "/",
history: hist,
routes: [parentRoute, redirectRoute, childRoute],
}
);
},
});

it("handles a top/middle/bottom route hierarchy where top and middle perform a redirect", async () => {
const { queryByText } = render(<Root />);
Expand Down
12 changes: 12 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ export type OmitOptional<T> = {
? P
: never]: T[P];
};

export type EmptyKeys<T> = keyof {
[K in keyof T as IsEmptyObject<T[K], true> extends true ? K : never]: T[K];
};

/**
* Marks any required property that can accept undefined as optional
*/
export type MarkOptionalLikePropertiesOptional<T> = Omit<T, EmptyKeys<T>> &
Partial<Pick<T, EmptyKeys<T>>>;

export type IsEmptyObject<
Obj,
ExcludeOptional extends boolean = false
Expand All @@ -24,6 +35,7 @@ export type IsEmptyObject<
? true
: false;

export type IsUnknown<T> = unknown extends T ? true : false;
export function assertIsDefined<T>(
val: T,
msg?: string
Expand Down
Loading

0 comments on commit 9a17ecc

Please sign in to comment.