Passing input to invoked actor

You will notice a type error with invoke:

Property 'input' is missing in type [...]

"invoke" reports a type error since a required input is missing.
"invoke" reports a type error since a required input is missing.

Since submitActor requires an input we need to pass it when executing the actor.

Take a moment to notice how since XState v5 most properties are completely type safe by default! 💯

We pass the input by defining input inside the invoke object:

const machine = setup({
  types: {
    context: {} as Context,
    events: {} as Event,
  },
  actors: { submitActor },
}).createMachine({
  context: initialContext,
  initial: "Editing",
  states: {
    Editing: {
      on: {
        "update-username": {
          actions: assign(({ event }) => ({
            username: event.username,
          })),
        },
        submit: { target: "Loading" },
      },
    },
    Loading: {
      invoke: {
        src: "submitActor",
        input: ({ event, context }) => {
          // Return the input for the submitActor
        },
      },
    },
  },
});

XState provides a helper function called assertEvent.

assertEvent will throw an error if the event is not of the expected type.

We use assertEvent to verify the event as submit, extract event and return the input for the submitActor:

const machine = setup({
  types: {
    context: {} as Context,
    events: {} as Event,
  },
  actors: { submitActor },
}).createMachine({
  context: initialContext,
  initial: "Editing",
  states: {
    Editing: {
      on: {
        "update-username": {
          actions: assign(({ event }) => ({
            username: event.username,
          })),
        },
        submit: { target: "Loading" },
      },
    },
    Loading: {
      invoke: {
        src: "submitActor",
        input: ({ event, context }) => {
          // 👇 Only "submit" contains `event` required as input to the actor
          assertEvent(event, "submit");
          return { event: event.event, context };
        },
      },
    },
  },
});

Since any event may cause the transition to the "Loading" state, we need to explicitly check that the event is of type submit before extracting event and returning the input for the submitActor.

Otherwise, the only thing we can do is throwing an error.

The submit event contains React.FormEvent<HTMLFormElement> that we pass to the submitActor as input.

type Event =
  | { type: "update-username"; username: string }
  | { type: "submit"; event: React.FormEvent<HTMLFormElement> };

const submitActor = fromPromise(
  async ({
    input,
  }: {
    input: { event: React.FormEvent<HTMLFormElement>; context: Context };
  }) => {
    input.event.preventDefault();
    await postRequest(input.context);
  }
);