State machine advent: A safer way to type events and state (11/24)
Tags: typescript, javascript, react
Published: 2019-12-11 14:40:41

I've worked quite a lot with Ember.js in the past and one of the things I really disliked is how often you have to use strings to perform simple tasks like getting the value of a property. If you like string-based APIs, I want to take a moment to explain why they are evil and then present a better, safer way to define events and state.

Throughout the series, we have seen many strings being used. Given a state definition like this

```typescript
interface lightSwitchStateSchema {
  states: {
    inactive: {};
    active: {};
  };
}
```

We used to determine the current state of the invoked machine using a string: 

`state.matches('active')`

While TypeScript got our back and will rightfully scream at us when we mistype or rename the state node in either the interface or the machine definition, we won't get any type errors for passing an incorrect string to `state.matches`. In other words, strings are a problem because they can't be statically typed and ultimately make it harder to refactor our code. 

A better way to write events and state nodes is to define them inside an enum (or a plain object if you are using JavaScript). This does make our code a bit more verbose but I believe the safety benefits outweigh the cost.


```typescript
enum LIGHT_SWITCH {
  "INACTIVE" = "inactive",
  "ACTIVE" = "active",
}

enum LIGHT_SWITCH_EVENT {
  "TOGGLE" = "TOGGLE",
}

interface LightSwitchStateSchema {
  states: {
    [LIGHT_SWITCH.INACTIVE]: {};
    [LIGHT_SWITCH.ACTIVE]: {};
  };
}

type LightSwitchEvent = { type: LIGHT_SWITCH_EVENT.TOGGLE };

const lightSwitchMachine = Machine<any, LightSwitchStateSchema, LightSwitchEvent>({
  id: 'lightSwitch',
  initial: LIGHT_SWITCH.INACTIVE,
  states: {
    [LIGHT_SWITCH.INACTIVE]: {
      on: {
        [LIGHT_SWITCH_EVENT.TOGGLE]: LIGHT_SWITCH.ACTIVE
      }
    },
    [LIGHT_SWITCH.ACTIVE]: {
      on: {
        [LIGHT_SWITCH_EVENT.TOGGLE]: LIGHT_SWITCH.INACTIVE
      }
    },
  }
});
```

If you want to send an event from a component or match the state, you can simply reuse the enums. 
```tsx
<Switch onChange={() => send(LIGHT_SWITCH_EVENT.TOGGLE)} checked={state.matches(LIGHT_SWITCH.ACTIVE)} />
```

In order to compare the machine definition from above with the one before the refactoring, take a look at [the blog post](https://dev.to/codingdive/state-machine-advent-statically-type-state-machines-with-typescript-7-24-445m) from a couple of days ago. We have replaced all the strings and object keys with our string enums and improved our type safety in the process. 🎉 

Decide for yourself if you think the tradeoff of having an easier time refactoring code and preventing typos is worth the expense of having more boilerplate. When I wrote my first state machines, I didn't think I would need to define all states and events with enums. Eventually, the refactoring experience became too frustrating to deal with. I then started to define all my events and states with string enums and wished I had done it sooner.
That being said, while I don't mind the additional boilerplate any more, my code did become a bit harder to read since making the change. If you are aware of any VSCode extension that could temporarily replace `[LIGHT_SWITCH.ACTIVE]` with the string value (`active`) with the click of a button, I'd love to know about it.


### 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.