1/7 GUI Tasks with React and XState: Counter
Tags: xstate, react, webdev, tutorial
Published: 2021-07-06 02:02:24

The first part of this article explores a couple learnings from implementing a Counter with XState and React. A Counter is the first of the [7 GUIs tasks](https://eugenkiss.github.io/7guis). The second, lengthier part of this article will walk through a full explanation of my solution.

You'll get a lot out of the first part even if you don't want to read through the full walk through.

## A Few Learnings

### The Core of a State Machine

The state machine that backs this Counter is one of the most basic XState machines you can build. I find that instructive because it shows me, once I cut away all the other features, what is at the core of defining a functioning state machine.

```javascript
const countingMachineDefinition = {
  initial: "counting",
  context: { count: 0 },
  states: {
    counting: {
      on: {
        INCREMENT: {
          actions: assign({
            count: (context) => context.count + 1,
          }),
        },
      },
    },
  },
};
```

1. The `initial` state that the machine will be in when it is first turned on.
2. The starting `context` that the machine will start with in its initial state. This is the secondary _state_, all the data beyond the current state itself.
3. A _finite_ set of `states`, at least one, that the machine can be in. In this case I just have the `counting` state.
4. Each state can have a set of one or more _events_ `on` which it will respond to with a transition and actions. In this case I just have the `INCREMENT` event. When this event is triggered in the `counting` state, it will transition to itself and an `assign` action will update the `count` in the `context`.

### Self Transitions

A state's event that doesn't specify a `target` will implicitly do a [self transition](https://xstate.js.org/docs/guides/transitions.html#self-transitions). In the state diagram, rather than the an arrow going from this state to another state, the arrow points to itself. This means that when that state receives that event, it will transition right back to itself. A transition always takes place.

### Internal Transitions

Because the `target` wasn't specified at all for `counting`'s `INCREMENT` event, the self transition will be an [internal transition](https://xstate.js.org/docs/guides/transitions.html#internal-transitions) (as opposed to an external transition). This means that on this internal transition, we don't _leave_ the current state node. The implications of that are that the [`entry`](https://xstate.js.org/docs/about/glossary.html#entry-action) and [`exit`](https://xstate.js.org/docs/about/glossary.html#exit-action) actions of that state will not be triggered.

Another, more explicit way to define an internal transition would be to specify the `internal` option as `true`.

```javascript
  states: {
    counting: {
      on: {
        INCREMENT: {
          internal: true,
          actions: assign({
            count: (context) => context.count + 1,
          }),
        },
      },
    },
  },
```

Another explicit way of doing the same thing here is to say outright that the `target` is `undefined`.

```javascript
  states: {
    counting: {
      on: {
        INCREMENT: {
          target: undefined,
          actions: assign({
            count: (context) => context.count + 1,
          }),
        },
      },
    },
  },
```

### External Transitions

Out of curiosity, let's look at a self transition that involves an external transition.

```javascript
  states: {
    counting: {
      on: {
        INCREMENT: {
          target: "counting",
          actions: assign({
            count: (context) => context.count + 1,
          }),
        },
      },
      entry: () => {
        console.log("Entering 'counting'");
      },
      exit: () => {
        console.log("Exiting 'counting'");
      },
    },
  },
```

We include the `target` option which points to the parent state, `counting`. To be sure that this brings back the `entry` and `exit` actions, I've add a couple logging actions. On each button click, we'll see the `exit` and then immediately the `entry` actions be triggered.

That's it... for my learnings from this super small state machine. If you are interested in digging into the full implementation keep reading.

_Otherwise, thanks for reading. If you enjoy my writing, consider [joining my newsletter](https://crafty-builder-6996.ck.page/e169c61186) or following me on [twitter](https://twitter.com/jbrancha)._

---

## Full Implementation Walk Through

The first of the 7 GUIs tasks is to create a [counter](https://eugenkiss.github.io/7guis/tasks#counter). This is a classic "Hello, World"-esque challenge for both UI frameworks and state management libraries. In our case, we are using React (a UI framework) and XState (a state management library). So we'll be exercising both aspects of this.

The task description is:

> The task is to build a frame containing a label or read-only textfield T and a button B. Initially, the value in T is “0” and each click of B increases the value in T by one.
> 
> Counter serves as a gentle introduction to the basics of the language, paradigm and toolkit for one of the simplest GUI applications imaginable. Thus, Counter reveals the required scaffolding and how the very basic features work together to build a GUI application. A good solution will have almost no scaffolding.

The author of 7 GUIs describes the goal of this first task as: "understanding the basic ideas of a language/toolkit."

In that spirit, the very first thing we'll have to understand is the interplay between React and XState.

Let's start by installing both XState and its React bindings into our React application.

```
$ yarn add xstate @xstate/react
```

The part that is core to XState is being able to turn a JSON description of a machine into a machine. This is done with the `createMachine` function which we will import.

```javascript
import { createMachine } from "xstate";
```

The React bindings part is when we interpret this machine definition into something that React can interact with the `useMachine` hook.

```javascript
import { useMachine } from '@xstate/react';
```

Let's define a counting machine in a separate `machine.js` file.

```javascript
import { createMachine } from "xstate";

const countingMachineDefinition = {
  initial: "counting",
  context: { count: 0 },
  states: {
    counting: {
      on: {
        INCREMENT: {
          actions: 'incrementCount',
        },
      },
    },
  },
};

export const countingMachine = createMachine(countingMachineDefinition);
```

This machine isn't quite ready, but it introduces most of the pieces we need to get our count on.

Our machine definition is made up, in this case, of `initial`, `context`, and `states`.

- `initial` specifies the state that this machine should start in when it is first interpreted. Our starting state is `counting`. That's also our only state.
- `context` is where we define an object containing any initial context for our machine. The only piece of context we are keeping track of is `count`. We will have it start at `0`.
- `states` lists the _finite_ set of states that make up this state machine. At any given time, our machine is going to be in one of these defined states. This is an extremely simple state machine that has a single state—`counting`.

Let's look a little closer at the `states` definition.

```javascript
  states: {
    counting: {
      on: {
        INCREMENT: {
          actions: 'incrementCount',
        },
      },
    },
  },
```

The `counting` state contains some information about itself. It tells us what events it responds to in the `on` object. Since we are only counting up, the `counting` state will only respond to the `INCREMENT` event.

Often the response to an event will be one or more actions as well as a transition to some other target state. This machine, only having one state, doesn't transition to another state. It implicitly does an [_internal_ self transition](https://xstate.js.org/docs/guides/transitions.html#self-transitions). It is like it is pointing to itself, but without making a show of it.

When the `INCREMENT` event is sent, the `incrementCount` action will be triggered. You might have noticed that there is no function definition for `incrementCount`.

In fact, if we were to start up this machine and send it the `INCREMENT` event, we would see the following warning in the console.

> Warning: No implementation found for action type 'incrementCount'

We still have to implement that.

We can either replace the `'incrementCount'` string with an inline function or we can define a function under that name in [an `actions` section](https://xstate.js.org/docs/guides/actions.html#declarative-actions).

The function is small enough that I'll just replace the string.

```javascript
import { createMachine, assign } from "xstate";

const countingMachineDefinition = {
  initial: "counting",
  context: { count: 0 },
  states: {
    counting: {
      on: {
        INCREMENT: {
          actions: assign({
            count: (context) => context.count + 1,
          }),
        },
      },
    },
  },
};

export const countingMachine = createMachine(countingMachineDefinition);
```

Notice I imported [`assign`](https://xstate.js.org/docs/guides/context.html#assign-action) from `xstate`. It is being used to generate an action handler that will update the machine's context. The only context that needs updating is `count`. Similar to React, Redux, and other state management libraries, the context value is updated using a function that provides the current context and returns the _updated_ context value.

So, each time the machine receives the `INCREMENT` event, it will trigger this `assign({ ... })` action that increments the count. Each subsequent event, will be working with the newest version of the `context` which will contain the incremented count.

And that's it, that's the counter machine.

Here is how we can use it (in a React component).

```javascript
import React from "react";
import { useMachine } from "@xstate/react";
import { countingMachine } from "../../src/machines/counter";

const Task1 = () => {
  const [state, send] = useMachine(countingMachine);

  return (
    <>
      <p>Count: {state.context.count}</p>
      <button onClick={() => send('INCREMENT')}>
        Increment
      </button>
    </>
  );
```

Each time the button is clicked, the `INCREMENT` event will be sent to the machine. The `count` context will be incremented and that value will trickle down to being rendered into the view via `{state.context.count}`.