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:
ts
import {createMachine } from 'xstate';constmachine =createMachine ({context : {userName : '',},initial : 'collectingFormDetails',states : {collectingFormDetails : {on : {SUBMIT : {actions : 'submitForm',target : 'submitted',},},},submitted : {},},},{actions : {submitForm : async (context ) => {awaitcreateUser (context .userName );},},});
ts
import {createMachine } from 'xstate';constmachine =createMachine ({context : {userName : '',},initial : 'collectingFormDetails',states : {collectingFormDetails : {on : {SUBMIT : {actions : 'submitForm',target : 'submitted',},},},submitted : {},},},{actions : {submitForm : async (context ) => {awaitcreateUser (context .userName );},},});
You might think that the sequence would work as follows:
- In the
collectingFormDetails
state, we receive theSUBMIT
event. - We execute the
submitForm
action and wait for it to finish. - When the
submitForm
action is done, we go to thesubmitted
state.
Instead, the sequence works like this:
- In the
collectingFormDetails
state, we receive theSUBMIT
event. - We execute the
submitForm
action and immediately transition to thesubmitted
state. - The result of the
submitForm
action is ignored.
To handle submitForm
properly, we need to use an actor:
ts
import {createMachine } from 'xstate';constmachine =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 v5services : {submitForm : async (context ) => {awaitcreateUser (context .userName );},},});
ts
import {createMachine } from 'xstate';constmachine =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 v5services : {submitForm : async (context ) => {awaitcreateUser (context .userName );},},});
Now, the sequence in the example above is:
- In the
collectingFormDetails
state, we receive theSUBMIT
event. - We go to the
submitting
state, where we execute thesubmitForm
actor. - When the
submitForm
actor is done, we go to thesubmitted
state. - If the
submitForm
actor errors, we go to theerrored
state.
The main difference between actions and actors is that actions can’t communicate back to the machine. Actors can.