Actions vs. actors
Sometimes it’s unclear whether you should use an action or an actor. Both appear to do similar things, executing side effects. Let’s break down the differences:
Actions are “fire-and-forget”; as soon as their execution starts, the statechart running the actions forgets about them. If you specify an action as async, the action won’t be awaited before moving to the next state. Below is an example:
const machine = createMachine(
  {
    context: {
      userName: '',
    },
    initial: 'collectingFormDetails',
    states: {
      collectingFormDetails: {
        on: {
          SUBMIT: {
            actions: 'submitForm',
            target: 'submitted',
          },
        },
      },
      submitted: {},
    },
  },
  {
    actions: {
      submitForm: async (context) => {
        await createUser(context.userName);
      },
    },
  }
);
You might think that the sequence would work as follows:
- In the collectingFormDetailsstate, we receive theSUBMITevent.
- We execute the submitFormaction and wait for it to finish.
- When the submitFormaction is done, we go to thesubmittedstate.
Instead, the sequence works like this:
- In the collectingFormDetailsstate, we receive theSUBMITevent.
- We execute the submitFormaction and immediately transition to thesubmittedstate.
- The result of the submitFormaction is ignored.
To handle submitForm properly, we need to use an actor:
const machine = createMachine(
  {
    context: {
      userName: '',
    },
    initial: 'collectingFormDetails',
    states: {
      collectingFormDetails: {
        on: {
          SUBMIT: {
            target: 'submitting',
          },
        },
      },
      submitting: {
        invoke: {
          src: 'submitForm',
          onDone: {
            target: 'submitted',
          },
          onError: {
            target: 'errored',
          },
        },
      },
      errored: {},
      submitted: {},
    },
  },
  {
    // `actors` in v5
    services: {
      submitForm: async (context) => {
        await createUser(context.userName);
      },
    },
  }
);
Now, the sequence in the example above is:
- In the collectingFormDetailsstate, we receive theSUBMITevent.
- We go to the submittingstate, where we execute thesubmitFormactor.
- When the submitFormactor is done, we go to thesubmittedstate.
- If the submitFormactor errors, we go to theerroredstate.
The main difference between actions and actors is that actions can’t communicate back to the machine. Actors can.