# Actions (/docs/actions)



Actions are fire-and-forget effects. When a state machine transitions, it may execute actions. Actions occur in response to events, and are typically defined on transitions in the `actions: [...]` property. Actions can also be defined for any transition that enters a state in the state's `entry: [...]` property, or for any transition that exits a state in the state's `exit: [...]` property.

<Callout>
  You can visualize your state machines and easily add actions in our drag-and-drop Stately editor. [Read more about actions in Stately’s editor](editor-actions-and-actors).
</Callout>

Actions can also be on a state’s `entry` or `exit`, also as a single action or an array.

```ts
import { setup } from 'xstate';

function trackResponse(response: string) {
  // ...
}

const feedbackMachine = setup({
  actions: {
    track: (_, params: { response: string }) => {
      trackResponse(params.response);
      // Tracks { response: 'good' }
    },
    showConfetti: () => {
      // ...
    }
  }
}).createMachine({
  // ...
  states: {
    // ...
    question: {
      on: {
        'feedback.good': {
          actions: [
            { type: 'track', params: { response: 'good' } }
          ]
        }
      },
      exit: [
        { type: 'exitAction' }
      ]
    }
    thanks: {
      entry: [
        { type: 'showConfetti' }
      ],
    }
  }
});
```

Examples of actions:

* Logging a message
* Sending a message to another [actor](actors)
* Updating context

Entry and exit actions [#entry-and-exit-actions]

Entry actions are actions that occur on any transition that enters a state node. Exit actions are actions that occur on any transition that exits a state node.

Entry and exit actions are defined using the `entry: [...]` and `exit: [...]` attributes on a state node. You can fire multiple entry and exit actions on a state. Top-level final states cannot have exit actions, since the machine is stopped and no further transitions can occur.

<EmbedMachine embedURL="https://stately.ai/registry/editor/embed/c447d996-cef1-421d-a422-8be695668764?mode=design&machineId=f46674a5-4da3-4aca-9900-17c6ef471f50" title="Feedback form" />

Action objects [#action-objects]

Action objects have an action `type` and an optional `params` object:

* The action `type` property describes the action. Actions with the same type have the same implementation.
* The action `params` property hold parameterized values that are relevant to the action.

```ts
import { setup } from 'xstate';

const feedbackMachine = setup({
  actions: {
    track: (_, params: { response: string }) => {
      /* ... */
    },
  },
}).createMachine({
  // ...
  states: {
    // ...
    question: {
      on: {
        'feedback.good': {
          actions: [
            // [!code highlight:6]
            {
              // Action type
              type: 'track',
              // Action params
              params: { response: 'good' },
            },
          ],
        },
      },
    },
  },
});
```

Dynamic action parameters [#dynamic-action-parameters]

You can dynamically pass parameters in the `params` property to action objects by using a function that returns the params. The function takes in an object that contains the current `context` and `event` as arguments.

```ts
import { setup } from 'xstate';

const feedbackMachine = setup({
  actions: {
    logInitialRating: (_, params: { initialRating: number }) => {
      // ...
    },
  },
}).createMachine({
  context: {
    initialRating: 3,
  },
  entry: [
    {
      type: 'logInitialRating',
      // [!code highlight:3]
      params: ({ context }) => ({
        initialRating: context.initialRating,
      }),
    },
  ],
});
```

This is a recommended approach for making actions more reusable, since you can define actions that do not rely on the machine’s `context` or `event` types.

```ts
import { setup } from 'xstate';

// [!code highlight:3]
function logInitialRating(_, params: { initialRating: number }) {
  console.log(`Initial rating: ${params.initialRating}`);
}

const feedbackMachine = setup({
  actions: { logInitialRating },
}).createMachine({
  context: { initialRating: 3 },
  entry: [
    {
      type: 'logInitialRating',
      // [!code highlight:3]
      params: ({ context }) => ({
        initialRating: context.initialRating,
      }),
    },
  ],
});
```

Inline actions [#inline-actions]

You can declare actions as inline functions:

```ts
import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
  entry: [
    // [!code highlight:4]
    // Inline action
    ({ context, event }) => {
      console.log(/* ... */);
    },
  ],
});
```

Inline actions are useful for prototyping and simple cases but we generally recommended using action objects.

Implementing actions [#implementing-actions]

You can setup the implementations for named actions in the `actions` property of the `setup(...)` function

```ts
import { setup } from 'xstate';

const feedbackMachine = setup({
  // [!code highlight:6]
  actions: {
    track: (_, params: { msg: string }) => {
      // Action implementation
      // ...
    },
  },
}).createMachine({
  // Machine config
  entry: [{ type: 'track', params: { msg: 'entered' } }],
});
```

You can also provide action implementations to override existing actions in the `machine.provide(...)` method, which creates a new machine with the same config but with the provided implementations:

```ts
import { createActor } from 'xstate';

const feedbackActor = createActor(
  // [!code highlight:9]
  feedbackMachine.provide({
    actions: {
      track: ({ context, event }, params) => {
        // Different action implementation
        // (overrides previous implementation)
        // ...
      },
    },
  }),
);
```

Built-in actions [#built-in-actions]

XState provides a number of useful built-in actions that are a core part of the logic of your state machines, and not merely side-effects.

<Callout type="warning">
  Built-in actions, such as `assign(…)`, `sendTo(…)`, and `raise(…)`, are **not imperative**; they return a special [action object](#action-objects) (e.g. `{ type: 'xstate.assign', … }`) that are interpreted by the state machine. Do not call built-in action in custom action functions.

  ```ts
  import { createMachine, assign, enqueueActions } from 'xstate';

  // ❌ This will have no effect
  const machine = createMachine({
    context: { count: 0 },
    // [!code highlight:5]
    entry: ({ context }) => {
      // This action creator only returns an action object
      // like { type: 'xstate.assign', ... }
      assign({ count: context.count + 1 });
    },
  });

  // ✅ This will work as expected
  const machine = createMachine({
    context: { count: 0 },
    // [!code highlight:3]
    entry: assign({
      count: ({ context }) => context.count + 1,
    }),
  });

  // ✅ Imperative built-in actions are available in `enqueueActions(…)`
  const machine = createMachine({
    context: { count: 0 },
    // [!code highlight:5]
    entry: enqueueActions(({ context, enqueue }) => {
      enqueue.assign({
        count: context.count + 1,
      });
    }),
  });
  ```
</Callout>

Assign action [#assign-action]

The `assign(...)` action is a special action that assigns data to the state context. The `assignments` argument in `assign(assignments)` is where assignments to context are specified.

Assignments can be an object of key-value pairs where the keys are `context` keys and the values are either static values or expressions that return the new value:

```ts
import { setup, assign } from 'xstate';

const countMachine = setup({
  types: {
    events: {} as { type: 'increment'; value: number },
  },
}).createMachine({
  context: {
    count: 0,
  },
  on: {
    increment: {
      // [!code highlight:3]
      actions: assign({
        count: ({ context, event }) => context.count + event.value,
      }),
    },
  },
});

const countActor = createActor(countMachine);
countActor.subscribe((state) => {
  console.log(state.context.count);
});
countActor.start();
// logs 0

countActor.send({ type: 'increment', value: 3 });
// logs 3

countActor.send({ type: 'increment', value: 2 });
// logs 5
```

For more dynamic assignments, the argument passed to `assign(...)` may also be a function that returns the partial or full `context` value:

```ts
import { setup, assign } from 'xstate';

const countMachine = setup({
  types: {
    events: {} as { type: 'increment'; value: number },
  },
}).createMachine({
  context: {
    count: 0,
  },
  on: {
    increment: {
      // [!code highlight:5]
      actions: assign(({ context, event }) => {
        return {
          count: context.count + event.value,
        };
      }),
    },
  },
});
```

<Callout type="warning">
  Do not mutate the `context` object. Instead, you should use the `assign(...)` action to update `context` immutably. If you mutate the `context` object, you may get unexpected behavior, such as mutating the `context` of other actors.
</Callout>

<Callout>
  You can create state machines with the `assign(...)` action in our drag-and-drop Stately editor. [Read more about built-in assign action in Stately’s editor](/docs/editor-actions-and-actors/#xstate-built-in-actions).
</Callout>

Raise action [#raise-action]

The raise action is a special action that *raises* an event that is received by the same machine. Raising an event is how a machine can “send” an event to itself:

```ts
import { createMachine, raise } from 'xstate';

const machine = createMachine({
  // ...
  // [!code highlight:1]
  entry: raise({ type: 'someEvent', data: 'someData' });
});
```

Internally, when an event is raised, it is placed into an “internal event queue”. After the current transition is finished, these events are processed in insertion order ([first-in first-out, or FIFO](https://en.wikipedia.org/wiki/FIFO_\(computing_and_electronics\))). External events are only processed once all events in the internal event queue are processed.

Raised events can be dynamic:

```ts
import { createMachine, raise } from 'xstate';

const machine = createMachine({
  // ...
  // [!code highlight:4]
  entry: raise(({ context, event }) => ({
    type: 'dynamicEvent',
    data: context.someValue,
  })),
});
```

Events can also be raised with a delay, which will not place them in the internal event queue, since they will not be immediately processed:

```ts
import { createMachine, raise } from 'xstate';

const machine = createMachine({
  // ...
  entry: raise(
    { type: 'someEvent' },
    // [!code highlight:1]
    { delay: 1000 }
  );
});
```

<Callout>
  You can create state machines with the `raise(...)` action in our drag-and-drop Stately editor. [Read more about the built-in raise action in Stately’s editor](/docs/editor-actions-and-actors/#xstate-built-in-actions).
</Callout>

Send-to action [#send-to-action]

The `sendTo(...)` action is a special action that sends an event to a specific actor.

```ts
import { createMachine, sendTo } from 'xstate';

const machine = createMachine({
  on: {
    transmit: {
      // [!code highlight:1]
      actions: sendTo('someActor', { type: 'someEvent' }),
    },
  },
});
```

The event can be dynamic:

```ts
import { createMachine, sendTo } from 'xstate';

const machine = createMachine({
  on: {
    transmit: {
      // [!code highlight:3]
      actions: sendTo('someActor', ({ context, event }) => {
        return { type: 'someEvent', data: context.someData };
      }),
    },
  },
});
```

The destination actor can be the actor ID or the actor reference itself:

```ts
import { createMachine, sendTo, fromPromise } from 'xstate';

const machine = createMachine({
  context: ({ spawn }) => ({
    someActorRef: spawn(fromPromise(/* ... */)),
  }),
  on: {
    transmit: {
      // [!code highlight:3]
      actions: sendTo(({ context }) => context.someActorRef, {
        type: 'someEvent',
      }),
    },
  },
});
```

Other options, such as `delay` and `id`, can be passed as the 3rd argument:

```ts
import { createMachine, sendTo } from 'xstate';

const machine = createMachine({
  on: {
    transmit: {
      actions: sendTo(
        'someActor',
        { type: 'someEvent' },
        // [!code highlight:4]
        {
          id: 'transmission',
          delay: 1000,
        },
      ),
    },
  },
});
```

Delayed actions can be cancelled by their `id`. See [`cancel(...)`](https://stately.ai/docs/actions#cancel-action).

<Callout>
  You can create state machines with the `sendTo(...)` action in our drag-and-drop Stately editor. [Read more about the built-in sendTo action in Stately’s editor](/docs/editor-actions-and-actors/#xstate-built-in-actions).
</Callout>

Send-parent action [#send-parent-action]

The `sendParent(...)` action is a special action that sends an event to the parent actor, if it exists.

<Callout>
  It is recommended to use `sendTo(...)` by to pass actor refs (e.g. the parent actor ref) to other actors via [input](./input.ts) or events and storing those actor refs in `context` rather than using `sendParent(...)`. This avoids tight coupling between actors and can be more type-safe.

  <details>
    <summary>
      Example using input:
    </summary>

    ```ts
    import { createMachine, sendTo } from 'xstate';

    const childMachine = createMachine({
      context: ({ input }) => ({
        parentRef: input.parentRef,
      }),
      on: {
        someEvent: {
          // [!code highlight:3]
          actions: sendTo(({ context }) => context.parentRef, {
            type: 'tellParentSomething',
          }),
        },
      },
    });

    const parentMachine = createMachine({
      // ...
      invoke: {
        id: 'child',
        src: childMachine,
        // [!code highlight:3]
        input: ({ self }) => ({
          parentRef: self,
        }),
      },
      on: {
        tellParentSomething: {
          actions: () => {
            console.log('Child actor told parent something');
          },
        },
      },
    });

    const parentActor = createActor(parentMachine);

    parentActor.start();
    ```
  </details>

  <details>
    <summary>
      Example using input (TypeScript):
    </summary>

    ```ts
    import { ActorRef, createActor, log, sendTo, setup, Snapshot } from 'xstate';

    // [!code highlight:5]
    type ChildEvent = {
      type: 'tellParentSomething';
      data?: string;
    };
    type ParentActor = ActorRef<Snapshot<unknown>, ChildEvent>;

    const childMachine = setup({
      types: {
        context: {} as {
          parentRef: ParentActor;
        },
        input: {} as {
          parentRef: ParentActor;
        },
      },
    }).createMachine({
      context: ({ input: { parentRef } }) => ({ parentRef }),
      // [!code highlight:4]
      entry: sendTo(({ context }) => context.parentRef, {
        type: 'tellParentSomething',
        data: 'Hi parent!',
      }),
    });

    export const parent = setup({
      actors: { child: childMachine },
    }).createMachine({
      // [!code highlight:6]
      invoke: {
        src: 'child',
        input: ({ self }) => ({
          parentRef: self,
        }),
      },
      on: {
        tellParentSomething: {
          actions: log(({ event: { data } }) => `Child actor says "${data}"`),
        },
      },
    });

    createActor(parent).start();
    ```
  </details>
</Callout>

Enqueue actions [#enqueue-actions]

The `enqueueActions(...)` action creator is a higher-level action that enqueues actions to be executed sequentially, without actually executing any of the actions. It takes a callback that receives the `context`, `event` as well as `enqueue` and `check` functions:

* The `enqueue(...)` function is used to enqueue an action. It takes an action object or action function:

  ```ts
  actions: enqueueActions(({ enqueue }) => {
    // Enqueue an action object
    enqueue({ type: 'greet', params: { message: 'hi' } });

    // Enqueue an action function
    enqueue(() => console.log('Hello'));

    // Enqueue a simple action with no params
    enqueue('doSomething');
  });
  ```

* The `check(...)` function is used to conditionally enqueue an action. It takes a guard object or a guard function and returns a boolean that represents whether the guard evaluates to `true`:
  ```ts
  actions: enqueueActions(({ enqueue, check }) => {
    if (check({ type: 'everythingLooksGood' })) {
      enqueue('doSomething');
    }
  });
  ```

* There are also helper methods on `enqueue` for enqueueing built-in actions:
  * `enqueue.assign(...)`: Enqueues an `assign(...)` action
  * `enqueue.sendTo(...)`: Enqueues a `sendTo(...)` action
  * `enqueue.raise(...)`: Enqueues a `raise(...)` action
  * `enqueue.spawnChild(...)`: Enqueues a `spawnChild(...)` action
  * `enqueue.stopChild(...)`: Enqueues a `stopChild(...)` action
  * `enqueue.cancel(...)`: Enqueues a `cancel(...)` action

Enqueued actions can be called conditionally, but they cannot be enqueued asynchronously.

```ts
import { createMachine, enqueueActions } from 'xstate';

const machine = createMachine({
  // ...
  entry: enqueueActions(({ context, event, enqueue, check }) => {
    // assign action
    enqueue.assign({
      count: context.count + 1,
    });

    // Conditional actions (replaces choose(...))
    if (event.someOption) {
      enqueue.sendTo('someActor', { type: 'blah', thing: context.thing });

      // other actions
      enqueue('namedAction');
      // with params
      enqueue({ type: 'greet', params: { message: 'hello' } });
    } else {
      // inline
      enqueue(() => console.log('hello'));

      // even built-in actions
    }

    // Use check(...) to conditionally enqueue actions based on a guard
    if (check({ type: 'someGuard' })) {
      // ...
    }

    // no return
  }),
});
```

You can use parameters with referenced enqueue actions:

```ts
import { setup, enqueueActions } from 'xstate';

const machine = setup({
  actions: {
    // [!code highlight:4]
    doThings: enqueueActions(({ enqueue }, params: { name: string }) => {
      enqueue({ type: 'greet', params: { name } });
      // ...
    }),
    greet: (_, params: { name: string }) => {
      console.log(`Hello ${params.name}!`);
    },
  },
}).createMachine({
  // ...
  // [!code highlight:4]
  entry: {
    type: 'doThings',
    params: { name: 'World' },
  },
});
```

Log action [#log-action]

The `log(...)` action is an easy way to log messages to the console.

```ts
import { createMachine, log } from 'xstate';

const machine = createMachine({
  on: {
    someEvent: {
      // [!code highlight:1]
      actions: log('some message'),
    },
  },
});
```

<Callout>
  You can create state machines with the `log(...)` action in our drag-and-drop Stately editor. [Read more about the built-in log action in Stately’s editor](/docs/editor-actions-and-actors/#xstate-built-in-actions).
</Callout>

Cancel action [#cancel-action]

The `cancel(...)` action cancels a delayed `sendTo(...)` or `raise(...)` action by their IDs:

```ts
import { createMachine, sendTo, cancel } from 'xstate';

const machine = createMachine({
  on: {
    event: {
      actions: sendTo(
        'someActor',
        { type: 'someEvent' },
        {
          // [!code highlight:1]
          id: 'someId',
          delay: 1000,
        },
      ),
    },
    cancelEvent: {
      // [!code highlight:1]
      actions: cancel('someId'),
    },
  },
});
```

Stop child action [#stop-child-action]

The `stopChild(...)` action stops a child actor. Actors can only be stopped from their parent actor:

```ts
import { createMachine, stopChild } from 'xstate';

const machine = createMachine({
  context: ({ spawn }) => ({
    spawnedRef: spawn(fromPromise(/* ... */), { id: 'spawnedId' }),
  }),
  on: {
    stopById: {
      // [!code highlight:1]
      actions: stopChild('spawnedId'),
    },
    stopByRef: {
      // [!code highlight:1]
      actions: stopChild(({ context }) => context.spawnedRef),
    },
  },
});
```

Modeling [#modeling]

If you only need to execute actions in response to events, you can create a [self-transition](/docs/transitions#self-transitions) that only has `actions: [ ... ]` defined. For example, a machine that only needs to assign to `context` in transitions may look like this:

```ts
import { createMachine, assign } from 'xstate';

const countMachine = createMachine({
  context: {
    count: 0,
  },
  // [!code highlight:12]
  on: {
    increment: {
      actions: assign({
        count: ({ context, event }) => context.count + event.value,
      }),
    },
    decrement: {
      actions: assign({
        count: ({ context, event }) => context.count - event.value,
      }),
    },
  },
});
```

Shorthands [#shorthands]

For simple actions, you can specify an action string instead of an action object. Though we prefer using objects for consistency.

```ts
import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
  // ...
  states: {
    // ...
    question: {
      on: {
        'feedback.good': {
          // [!code highlight:1]
          actions: ['track'],
        },
      },
    },
  },
});
```

Actions and TypeScript [#actions-and-typescript]

<Callout>
  **XState v5 requires TypeScript version 5.0 or greater.**

  For best results, use the latest TypeScript version. [Read more about XState and TypeScript](typescript)
</Callout>

To strongly setup action types, use the `setup({ ... })` function and place the action implementations in the `actions: { ... }` object. The key is the action type and the value is the action function implementation.

You should also strongly type the parameters of the action function, which are passed as the second argument to the action function.

```ts
import { setup } from 'xstate';

const machine = setup({
  // [!code highlight:8]
  actions: {
    track: (_, params: { response: string }) => {
      // ...
    },
    increment: (_, params: { value: number }) => {
      // ...
    },
  },
}).createMachine({
  // ...
  entry: [
    { type: 'track', params: { response: 'good' } },
    { type: 'increment', params: { value: 1 } },
  ],
});
```

If you are not using `setup({ ... })` (strongly recommended), you can strongly type the `actions` of your machine in the `types.actions` property of the machine config.

```ts
import { createMachine } from 'xstate';

const machine = createMachine({
  types: {} as {
    // [!code highlight:8]
    actions:
      | {
          type: 'track';
          params: {
            response: string;
          };
        }
      | { type: 'increment'; params: { value: number } };
  },
  // ...
  entry: [
    { type: 'track', params: { response: 'good' } },
    { type: 'increment', params: { value: 1 } },
  ],
});
```

Actions cheatsheet [#actions-cheatsheet]

Cheatsheet: entry and exit actions [#cheatsheet-entry-and-exit-actions]

```ts
import { createMachine } from 'xstate';

const machine = createMachine({
  // [!code highlight:3]
  // Entry action on root
  entry: [{ type: 'entryAction' }],
  exit: [{ type: 'exitAction' }],
  initial: 'start',
  states: {
    start: {
      // [!code highlight:2]
      entry: [{ type: 'startEntryAction' }],
      exit: [{ type: 'startExitAction' }],
    },
  },
});
```

Cheatsheet: transition actions [#cheatsheet-transition-actions]

```ts
import { createMachine } from 'xstate';

const machine = createMachine({
  on: {
    someEvent: {
      actions: [
        // [!code highlight:2]
        { type: 'doSomething' },
        { type: 'doSomethingElse' },
      ],
    },
  },
});
```

Cheatsheet: inline action functions [#cheatsheet-inline-action-functions]

```ts
import { createMachine } from 'xstate';

const machine = createMachine({
  on: {
    someEvent: {
      actions: [
        // [!code highlight:3]
        ({ context, event }) => {
          console.log(context, event);
        },
      ],
    },
  },
});
```

Cheatsheet: setting up actions [#cheatsheet-setting-up-actions]

```ts
import { setup } from 'xstate';

const someAction = () => {
  //...
};

const machine = setup({
  actions: {
    someAction,
  },
}).createMachine({
  entry: [
    // [!code highlight:1]
    { type: 'someAction' },
  ],
  // ...
});
```

Cheatsheet: providing actions [#cheatsheet-providing-actions]

```ts
import { setup } from 'xstate';

const someAction = () => {
  //...
};

const machine = setup({
  actions: {
    someAction,
  },
}).createMachine({
  // ...
});

const modifiedMachine = machine.provide({
  someAction: () => {
    // Overridden action implementation
  },
});
```

Cheatsheet: assign action [#cheatsheet-assign-action]

With property assigners [#with-property-assigners]

```ts
import { createMachine, assign } from 'xstate';

const countMachine = createMachine({
  context: {
    count: 0,
  },
  on: {
    increment: {
      // [!code highlight:5]
      actions: assign({
        count: ({ context, event }) => {
          return context.count + event.value;
        },
      }),
    },
  },
});
```

With function assigners [#with-function-assigners]

```ts
import { createMachine, assign } from 'xstate';

const countMachine = createMachine({
  context: {
    count: 0,
  },
  on: {
    increment: {
      // [!code highlight:5]
      actions: assign(({ context, event }) => {
        return {
          count: context.count + event.value,
        };
      }),
    },
  },
});
```

Cheatsheet: raise action [#cheatsheet-raise-action]

```ts
import { createMachine, raise } from 'xstate';

const machine = createMachine({
  on: {
    someEvent: {
      // [!code highlight:1]
      actions: raise({ type: 'anotherEvent' }),
    },
  },
});
```

Cheatsheet: send-to action [#cheatsheet-send-to-action]

```ts
import { createMachine, sendTo } from 'xstate';

const machine = createMachine({
  on: {
    transmit: {
      // [!code highlight:1]
      actions: sendTo('someActor', { type: 'someEvent' }),
    },
  },
});
```

Cheatsheet: enqueue actions [#cheatsheet-enqueue-actions]

```ts
import { createMachine, enqueueActions } from 'xstate';

const machine = createMachine({
  // [!code highlight:15]
  entry: enqueueActions(({ enqueue, check }) => {
    enqueue({ type: 'someAction' });

    if (check({ type: 'someGuard' })) {
      enqueue({ type: 'anotherAction' });
    }

    enqueue.assign({
      count: 0,
    });

    enqueue.sendTo('someActor', { type: 'someEvent' });

    enqueue.raise({ type: 'anEvent' });
  }),
});
```
