State machine advent: Invoking a reusable state machine (21/24)
Tags: typescript, javascript, react
Published: 2019-12-21 18:35:04

[Two days ago](https://dev.to/codingdive/state-machine-advent-asynchronous-code-in-xstate-102p), we learned about services for the very first time. In particular, we wrote our first service by invoking a promise. Today, we want to look at one of my favorite things to do in XState, invoking a machine as a service. When modeling state machines and statecharts, we should always strive to keep them as small as possible. We should write multiple small machines and wire them together using cross-machine communication and the actor model. The concept of [invoking a machine as a service]( https://xstate.js.org/docs/guides/communication.html#invoking-machines) is quite similar to the promise. Just like we had to wait for the invoked promise to be resolved or rejected, a machine can invoke a child machine and be notified once the child machine has reached its final state via the `onDone` handler.

In XState, a machine can mark the last state - from which it doesn't define any more state transitions; therefore, it can't have events - with `type: final`.

To try this in action, we want to define some machines that allow us to play rock paper scissors. Where I'm from, most people scream "rock paper scissors" out loud before deciding on the action they take. 😄 Let's ensure our machine does the same thing by logging "rock", "paper", "scissor" with a small delay. Despite not focus of today's lecture, I wanted to sneak [delayed transitions](https://xstate.js.org/docs/guides/delays.html#delayed-transitions) into the post because the ability to set a delay from one state to another with each state node performing a different action is really powerful and one of the reasons why XState resonates so well with animations.

# Child machine
Let's get to building by defining some types. 🛠️


```typescript
enum ROCK_PAPER_SCISSORS {
  'ROCK' = 'ROCK',
  'PAPER' = 'PAPER',
  'SCISSORS' = 'SCISSORS',
}

interface RockPaperScissorsStateSchema {
  states: {
    screamRock: {};
    screamPaper: {};
    screamScissors: {};
    randomizingAction: {};
    played: {};
  };
}

interface RockPaperScissorsContext {
  action: ROCK_PAPER_SCISSORS | 'NONE';
}
```

Let's implement the machine. Notice how we don't need any events in the child machine (third type argument is any when calling the `Machine` factory function). I'll explain why we don't define events (other than the null event) in a minute. For now, appreciate the fact that we are about to define our very first internal machine that will be invoked by another machine instead of a component 🤩


```typescript
import { Machine, assign, actions } from 'xstate';
const { log } = actions;

const rockPaperScissorsMachine = Machine<RockPaperScissorsContext, RockPaperScissorsStateSchema, any>({
  id: 'rockPaperScissors',
  initial: 'screamRock',
  context: {
    action: 'NONE',
  },
  states: {
    screamRock: {
      entry: log((context, event) => "ROCK...", 'RockPaperScissors'),
      after: {
        // 1/10th later transition to scream "PAPER"
        100: 'screamPaper',
      },
    },
    screamPaper: {
      entry: log((context, event) => "PAPER...", 'RockPaperScissors'),
      after: {
        // 1/10th second later transition to scream "SCISSORS"
        100: 'screamScissors',
      },
    },
    screamScissors: {
      entry: log((context, event) => "SCISSORS...", 'RockPaperScissors'),
      after: {
      // 1/10th second later transition to randomizingAction
        100: 'randomizingAction',
      },
    },
    randomizingAction: {
      on: {
        '': {
          actions: assign({
            // click on the linked codesandbox at the very end
            // if you are curious about the randomEnum function 
            action: () => randomEnum(ROCK_PAPER_SCISSORS),
          }),
          target: 'played',
        },
      },
    },
    played: {
      type: 'final',
      data: {
        performedAction: (context, event) => context.action,  
      }
    },
  },
});
```

See how the machine can additionally define some extended state via `data` that the parent machine can read once the child reaches the `final` state. We can refer to it as [done data](https://xstate.js.org/docs/guides/communication.html#done-data).

Before moving on to the parent machine, let me tell you why we have defined a `randomizingAction` state with a [null event](https://dev.to/codingdive/state-machine-advent-the-power-of-null-events-3kn8). Right before the machine transitions to the next state (`played`), a randomized `ROCK_PAPER_SCISSORS` value is assigned to the action property of the machine's `context`. Alternatively, just like we perform a log action when entering the screaming state nodes, the action of changing the context could've also been performed as an `entry` action of the `played` state.
On the contrary, once we have entered the `played` state, we'd usually expect the action to be already set to `ROCK | PAPER | SCISSORS`. To prevent ambiguity, we want to set the value before entering the state node; hence, we added a very explicit state node `randomizingAction`. Don't be frugal when it comes to defining state nodes, they can add a lot of clarity to the code we write.

___
# Parent machine

Our rock paper scissors machine should be invoked by a user. Let's represent the user with an `ActorMachine`.

```typescript
interface ActorStateSchema {
  states: {
    idle: {};
    playing: {};
    played: {};
  };
}

interface ActorContext {
  playedAction?: ROCK_PAPER_SCISSORS;
}

type ActorEvent = { type: 'PLAY' };
```

As you can see, there are quite some things we had to repeat like `playing`, `played` and the action value inside the machine's context. Former state is essential to invoke the child machine and once the machine has reached the final state, the parent machine will also transition to a state indicating that a game has been `played`. As established before, there is no need to define state nodes sparingly and we get some nice benefits from having defined a parent machine. In particular, we managed to encapsulate the few states and actions needed to play the game into a distinct rock paper scissors machine. By invoking the machine, it doesn't need to be aware of the `idle` state and `PLAY` event of the parent machine. As a benefit, the machine has a single responsibility and because of its minimal API surface, it can be easily reused. 


```typescript
const actorMachine = Machine<ActorContext, ActorStateSchema, ActorEvent>({
  id: 'player',
  initial: 'idle',
  context: {
    playedAction: undefined,
  },
  states: {
   idle: {
     on: {
       'PLAY': 'playing',
     },
   },
   playing: {
      invoke: {
        id: 'playRockPaperScissor',
        src: rockPaperScissorsMachine,
        onDone: {
          target: 'played',
          actions: assign({ playedAction: (context, event) => event.data.performedAction }),
        }
      }
    },
    played: {
        on: {
          /** Let's play again :) */
          PLAY: "playing"
        }
    },
  },
});
```

Looking at the parent machine, the most important part is clearly when the `rockPaperScissorsMachine` gets invoked. Once the event handler is called to indicate that the child machine has finished its execution, we assign the `done data` to the parent context. Just one of multiple ways to handle cross-machine communication. For learning purposes, I named the extended state differently every time so you can clearly tell the difference. You could also call them by the same name e.g `action` which might make the code easier to look at.

- `action`: property in the context of the child machine
- `performedAction`: property of the `done data` inside the final state of the child machine. Gets assigned the `action` value
- `playedAction`: property in the context of the parent machine. Gets assigned the `performAction` value 

You can see everything working together smoothly in this [codesandbox](https://codesandbox.io/s/sleepy-lederberg-mtm8y). 

### About this series
Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.

The first couple of days will be spent on the fundamentals before we'll progress to more advanced concepts.