Skip to content

Instantly share code, notes, and snippets.

@tlimpanont
Last active August 7, 2022 14:43
Show Gist options
  • Save tlimpanont/2cbbf369dccc902277e4b4704b837976 to your computer and use it in GitHub Desktop.
Save tlimpanont/2cbbf369dccc902277e4b4704b837976 to your computer and use it in GitHub Desktop.
xState form submission machine
import { describe, expect, it } from "vitest";
import { interpret } from "xstate";
import { formSubmissionMachine } from "../formSubmissionMachine";
import { waitFor } from "xstate/lib/waitFor";
describe("formSubmissionMachine", () => {
it("should validate name and save in DB", async () => {
const machine = formSubmissionMachine.withConfig({
services: {
validateName: (context) => {
return Promise.resolve({
config: {
params: {
name: "Henk",
},
},
});
},
postDB: (context) => {
return Promise.resolve();
},
},
});
const service = interpret(machine);
service.start();
expect(service.state.value).toBe("enteringData");
service.send({ type: "VALIDATE", value: { name: "Henk" } });
expect(service.state.value).toBe("validating");
await waitFor(service, (state) => state.matches("readyToSubmit"));
expect(service.state.context.name).toBe("Henk");
service.send({ type: "SAVE_IN_DB", value: { name: "Henk" } });
await waitFor(service, (state) => state.matches("dataSaved"));
expect(service.state.done).toBeTruthy();
});
it("should failed to validate name", async () => {
const machine = formSubmissionMachine.withConfig({
services: {
validateName: (context) => {
return Promise.reject();
},
},
});
const service = interpret(machine);
service.start();
expect(service.state.value).toBe("enteringData");
service.send({ type: "VALIDATE", value: { name: "Henk" } });
expect(service.state.value).toBe("validating");
await waitFor(service, (state) => state.matches("enteringData"));
expect(service.state.context.name).toBeUndefined();
});
it("should failed to save to DB", async () => {
const machine = formSubmissionMachine.withConfig({
services: {
validateName: (context) => {
return Promise.resolve({
config: {
params: {
name: "Henk",
},
},
});
},
postDB: (context) => {
return Promise.reject();
},
},
});
const service = interpret(machine);
service.start();
service.send({ type: "VALIDATE", value: { name: "Henk" } });
await waitFor(service, (state) => state.matches("readyToSubmit"));
service.send({ type: "SAVE_IN_DB", value: { name: "Henk" } });
await waitFor(service, (state) => state.matches("enteringData"));
});
});
import { assign, createMachine } from "xstate";
import { validateName } from "../services/validateName";
import { postDB } from "../services/postDB";
export type FormSubmissionContext = {
name?: string;
};
export type FormSubmissionEvent =
| { type: "VALIDATE"; value: FormSubmissionContext }
| { type: "SAVE_IN_DB"; value: FormSubmissionContext }
| { type: "EDIT_DATA"; value: FormSubmissionContext };
export type FormSubmissionTypeState =
| {
value: "enteringData";
context: FormSubmissionContext;
}
| {
value: "validating";
context: FormSubmissionContext;
}
| {
value: "readyToSubmit";
context: FormSubmissionContext;
}
| {
value: "sendingToDB";
context: FormSubmissionContext;
}
| {
value: "dataSaved";
context: FormSubmissionContext;
}
| {
value: "dataFailedToSave";
context: FormSubmissionContext;
};
export const formSubmissionMachine = createMachine<
FormSubmissionContext,
FormSubmissionEvent,
FormSubmissionTypeState
>(
{
predictableActionArguments: true,
initial: "enteringData",
context: {
name: "",
},
states: {
enteringData: {
on: {
VALIDATE: {
target: "validating",
},
},
},
validating: {
invoke: {
id: "validateName",
src: validateName,
onDone: {
target: "readyToSubmit",
actions: assign((context, event) => {
return {
name: event.data.config.params.name,
};
}),
},
onError: {
target: "enteringData",
actions: assign((context, event) => {
return {
name: undefined,
};
}),
},
},
},
readyToSubmit: {
on: {
SAVE_IN_DB: {
target: "sendingToDB",
cond: (context) => context.name !== undefined,
},
VALIDATE: {
target: "validating",
},
},
},
sendingToDB: {
invoke: {
id: "postDB",
src: postDB,
onDone: {
target: "dataSaved",
},
onError: {
target: "enteringData",
},
},
},
dataSaved: {
type: "final",
},
},
},
{
actions: {},
services: {},
guards: {},
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment