Stately
State Machines

Input

Input refers to the data provided to a state machine that influences its behavior. In XState, you provide input when creating an actor using the second argument of the createActor(machine, { input }) function:

import { createActor, setup } from 'xstate';

const feedbackMachine = setup({
  types: {
    context: {} as {
      userId: string;
      feedback: string;
      rating: number;
    },
    input: {} as {
      userId: string;
      defaultRating: number;
    },
  },
}).createMachine({
  context: ({ input }) => ({
    userId: input.userId,
    feedback: '',
    rating: input.defaultRating,
  }),
  // ...
});

const feedbackActor = createActor(feedbackMachine, {
  input: {
    userId: '123',
    defaultRating: 5,
  },
});

Input is coming to Stately Studio’s editor soon.

Creating actors with input

You can pass input to any kind of actor by reading this input from the input property of the first argument to actor logic creators, such as fromPromise(), fromTransition(), fromObservable(), and other actor logic creators.

Input with fromPromise():

import { createActor, fromPromise } from 'xstate';

const userFetcher = fromPromise(({ input }: { input: { userId: string } }) => {
  return fetch(`/users/${input.userId}`).then((res) => res.json());
});

const userFetcherActor = createActor(userFetcher, {
  input: {
    userId: '123',
  },
}).start();

userFetcherActor.onDone((data) => {
  console.log(data);
  // logs the user data for userId 123
});

Input with fromTransition():

import { createActor, fromTransition } from 'xstate';

const counter = fromTransition((state, event)) => {
  if (event.type === 'INCREMENT') {
    return { count: state.count + 1 };
  }
  return state;
}, ({ input }: { input: { startingCount?: number } }) => ({
  count: input.startingCount ?? 0,
});

const counterActor = createActor(counter, {
  input: {
    startingCount: 10,
  }
});

Input with fromObservable():

import { createActor, fromObservable } from 'xstate';
import { interval } from 'rxjs';

const intervalLogic = fromObservable(
  ({ input }: { input: { interval: number } }) => {
    return interval(input.interval);
  },
);

const intervalActor = createActor(intervalLogic, {
  // highlight-start
  input: {
    interval: 1000,
  },
});

intervalActor.start();

Initial event input

When an actor is started, it will automatically send a special event named xstate.init to itself. If input is provided to the createActor(logic, { input }) function, it will be included in the xstate.init event:

import { createActor, createMachine } from 'xstate';

const feedbackMachine = createMachine({
  entry: ({ event }) => {
    console.log(event.input);
    // logs { userId: '123', defaultRating: 5 }
  },
  // ...
});

const feedbackActor = createActor(feedbackMachine, {
  input: {
    userId: '123',
    defaultRating: 5,
  },
}).start();

Invoking actors with input

You can provide input to invoked actors via the input property of the invoke configuration:

import { createActor, setup } from 'xstate';

const feedbackMachine = setup({
  actors: {
    liveFeedback: fromPromise(({ input }: { input: { domain: string } }) => {
      return fetch(`https://${input.domain}/feedback`).then((res) =>
        res.json(),
      );
    }),
  },
}).createMachine({
  invoke: {
    src: 'liveFeedback',
    input: {
      domain: 'stately.ai',
    },
  },
});

The invoke.input property can be a static input value or a function that returns the input value. The function will be called with an object that contains the current context and event:

import { createActor, setup } from 'xstate';

const feedbackMachine = setup({
  actors: {
    fetchUser: fromPromise(({ input }) => {
      return fetch(`/users/${input.userId}`).then((res) => res.json());
    }),
  },
}).createMachine({
  context: {
    userId: '',
    feedback: '',
    rating: 0,
  },
  invoke: {
    src: 'fetchUser',
    input: ({ context }) => ({ userId: context.userId }),
  },
  // ...
});

Spawning actors with input

You can provide input to spawned actors via the input property of the spawn configuration:

import { createActor, setup, type AnyActorRef } from 'xstate';

const feedbackMachine = setup({
  types: {
    context: {} as {
      userId: string;
      feedback: string;
      rating: number;
      emailRef: AnyActorRef;
    },
  },
  actors: {
    emailUser: fromPromise(({ input }: { input: { userId: string } }) => {
      return fetch(`/users/${input.userId}`, {
        method: 'POST',
        // ...
      });
    }),
  },
}).createMachine({
  context: {
    userId: '',
    feedback: '',
    rating: 0,
    emailRef: null,
  },
  // ...
  on: {
    'feedback.submit': {
      actions: assign({
        emailRef: ({ context, spawn }) => {
          return spawn('emailUser', {
            input: { userId: context.userId },
          });
        },
      }),
    },
  },
  // ...
});

Use-cases

Input is useful for creating reusable machines that can be configured with different input values.

  • Replaces the old way of writing a factory function for machines:
// Old way: using a factory function
const createFeedbackMachine = (userId, defaultRating) => {
  return createMachine({
    context: {
      userId,
      feedback: '',
      rating: defaultRating,
    },
    // ...
  });
};

const feedbackMachine1 = createFeedbackMachine('123', 5);

const feedbackActor1 = createActor(feedbackMachine1).start();

// New way: using input
const feedbackMachine = createMachine({
  context: ({ input }) => ({
    userId: input.userId,
    feedback: '',
    rating: input.defaultRating,
  }),
  // ...
});

const feedbackActor = createActor(feedbackMachine, {
  input: {
    userId: '123',
    defaultRating: 5,
  },
});

Passing new data to an actor

Changing the input will not cause the actor to be restarted. You need to send an event to the actor to pass the new data to the actor:

const Component = (props) => {
  const feedbackActor = useActor(feedbackMachine, {
    input: {
      userId: props.userId,
      defaultRating: props.defaultRating,
    },
  });

  useEffect(() => {
    feedbackActor.send({
      type: 'userId.change',
      userId: props.userId,
    });
  }, [props.userId]);

  // ...
};

Input and TypeScript

XState v5 requires TypeScript version 5.0 or greater.

For best results, use the latest TypeScript version. Read more about XState and TypeScript

You can strongly type the input of your machine in the types.input property of the machine setup.

import { createActor, setup } from 'xstate';

const machine = setup({
  types: {
    input: {} as {
      userId: string;
      defaultRating: number;
    };
    context: {} as {
      userId: string;
      feedback: string;
      rating: number;
    };
  },
}).createMachine({
  context: ({ input }) => ({
    userId: input.userId,
    feedback: '',
    rating: input.defaultRating,
  }),
});

const actor = createActor(machine, {
  input: {
    userId: '123',
    defaultRating: 5,
  },
});

Input cheatsheet

Use our XState input cheatsheet below to get started quickly.

Cheatsheet: providing input

const feedbackActor = createActor(feedbackMachine, {
  input: {
    userId: '123',
    defaultRating: 5,
  },
});

Cheatsheet: providing input to invoked actors

const feedbackMachine = createMachine({
  invoke: {
    src: 'liveFeedback',
    input: {
      domain: 'stately.ai',
    },
  },
});

Cheatsheet: providing dynamic input to invoked actors

const feedbackMachine = createMachine({
  context: {
    userId: 'some-user-id',
  },
  invoke: {
    src: 'fetchUser',
    input: ({ context }) => ({ userId: context.userId }),
  },
});

Cheatsheet: providing dynamic input from event properties to invoked actors

const feedbackMachine = createMachine({
  types: {
    events:
      | { type: 'messageSent', message: string }
      | { type: 'incremented', count: number },
  },
  invoke: {
    src: 'fetchUser',
    input: ({ event }) => {
      assertEvent(event, 'messageSent');
      return {
        message: event.message,
      };
    },
  },
});

Cheatsheet: providing input to spawned actors

const feedbackMachine = createMachine({
  context: {
    userId: '',
  },
  // ...
  on: {
    'feedback.submit': {
      actions: assign({
        emailRef: ({ context, spawn }) => {
          return spawn('emailUser', {
            input: { userId: context.userId },
          });
        },
      }),
    },
  },
  // ...
});

On this page