Basic form flow using xstate and react
Tags: beginners, react, xstate
Published: 2019-12-29 18:15:08

---
title: Basic form flow using xstate and react
published: true
description: Learn how to implement a simple form logic using xstate and react,
cover_image: https://thepracticaldev.s3.amazonaws.com/i/hei9fhgqn5hmzbe4u55a.jpg
tags: beginner, react, xstate
---

I am on a journey to learn how to use [xstate](https://xstate.js.org/docs/). As I learn, I decided to share pieces of my learnings. The goal is to increase my understanding and to add more `xstate` implementation examples to the ecosystem. 

The app we will be building is a simple form that logs the submitted text to the console. This sounds super simple and can easily be implemented with react. However, how would we build this using xstate? 

Lets start by building the UI components of our app. Note that I am using [CodeSandbox](https://codesandbox.io) to write my code. 

```
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <h1>Hello World!</h1>
      <h2>Submit to log some text on the console</h2>
      <form>
        <input
          type="text"
          placeholder="Enter text"
        />
        <button>submit</button>
      </form>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

```

This is what our app looks like in the browser:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/u2dbm43j3fdi5o47yyel.PNG)

Alright, lets install `xstate` and `@xstate/react` as dependencies in our application. Then import them and start thinking about our simple form `State Machine`

```
...
import { Machine } from "xstate";

const simpleFormMachine = Machine(
  {
    id: "simpleForm",
    initial: "idle",
    states: {
      idle: {},
      submitting: {}
      }
    }
  }
);

...
```

As a start we give our machine an unique id and an initial state. Next we enter our expected states. 

This is the flow we are expecting: the form is idle then the user starts an event, which is the "typing" event. In the "typing" event the form is still in idle state. Then when the user presses the submit button, the form starts submitting the data. That is when we should implement the logging logic.

Based on the above narrative I saw fit we have two states for our form: `idle state` and `submitting state`.

We have a basic idea of what our `state machine` will look like. However, where can we store the state of the input value? There are infinite representations of this state (whatever the user will input), which should lead us to conclude that we need to use the `xstate` `extended state`, named `context`. Then we can pass this value to our `jsx` input via the `@xstate/react` library.

```
import React from "react";
import ReactDOM from "react-dom";
import { Machine } from "xstate";
import { useMachine } from "@xstate/react";

import "./styles.css";

const simpleFormMachine = Machine(
  {
    id: "simpleForm",
    initial: "idle",
    context: {
      inputValue: ""
    },
    states: {
      idle: {},
      submitting: {}
  }
);

function App() {
  const [current, send] = useMachine(simpleFormMachine);
  return (
    <div className="App">
      <h1>Hello World!</h1>
      <h2>Submit to log some text on the console</h2>
      <form>
        <input
          type="text"
          placeholder="Enter text"
          value={current.context.inputValue}
        />
        <button>submit</button>
      </form>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

```

To test that our input value is connected to our `state machine's` context, simply change the initial value of `inputValue` and you will see the change reflected in the input.

Next lets implement `onChange` using `xstate`. When the user starts inserting text in the input box the onChange event is triggered. When this happens we should send a message to the `state machine` telling it that the user is currently typing. Our machine should then implement an action to assign our context to the value being inputted by the user.

```
...

const simpleFormMachine = Machine(
  {
    id: "simpleForm",
    initial: "idle",
    context: {
      inputValue: ""
    },
    states: {
      idle: {
        on: {
          TYPING: {
            actions: "typing"
          }
        }
      },
      submitting: {}
    }
  },
  {
    actions: {
      typing: assign((ctx, e) => ({ inputValue: e.value }))
    }
  }
);

function App() {
  const [current, send] = useMachine(simpleFormMachine);
  return (
    <div className="App">
      <h1>Hello World!</h1>
      <h2>Submit to log some text on the console</h2>
      <form>
        <input
          type="text"
          placeholder="Enter text"
          value={current.context.inputValue}
          onChange={e =>
            send({
              type: "TYPING",
              value: e.target.value
            })
          }
        />
        <button>submit</button>
      </form>
    </div>
  );
}

...
```

Now lets implement our submit logic. This happens when the form is submitted, via the submit button or the `enter` key. In this case we want our form to send an event indicating it is submitting. Our machine will then change from `idle` to `submitting` state. As we enter this state we should log the current context value to the console. 

```
...

const simpleFormMachine = Machine(
  {
    id: "simpleForm",
    initial: "idle",
    context: {
      inputValue: ""
    },
    states: {
      idle: {
        on: {
          TYPING: {
            actions: "typing"
          },
          SUBMIT: "submitting"
        }
      },
      submitting: {
        entry: "log"
      }
    }
  },
  {
    actions: {
      typing: assign((ctx, e) => ({ inputValue: e.value })),
      log: (ctx, e) => console.log(ctx.inputValue)
    }
  }
);

function App() {
  const [current, send] = useMachine(simpleFormMachine);
  return (
    <div className="App">
      <h1>Hello World!</h1>
      <h2>Submit to log some text on the console</h2>
      <form
        onSubmit={e => {
          e.preventDefault();
          send("SUBMIT");
        }}
      >
        <input
          type="text"
          placeholder="Enter text"
          value={current.context.inputValue}
          onChange={e =>
            send({
              type: "TYPING",
              value: e.target.value
            })
          }
        />
        <button>submit</button>
      </form>
    </div>
  );
}
...
```

The app is almost complete. However, the only issue we have is that we are not going back to an idle state after submitting the form. Lets send a second event after the 'SUBMIT' event. Lets call it 'STOPPED_TYPING'. when this event occurs we will go back to the idle state and we should trigger an action to clear the form.

```
...

const simpleFormMachine = Machine(
  {
    id: "simpleForm",
    initial: "idle",
    context: {
      inputValue: ""
    },
    states: {
      idle: {
        on: {
          TYPING: {
            actions: "typing"
          },
          SUBMIT: "submitting"
        }
      },
      submitting: {
        entry: "log",
        on: {
          STOPPED_TYPING: {
            target: "idle",
            actions: "clear"
          }
        }
      }
    }
  },
  {
    actions: {
      typing: assign((ctx, e) => ({ inputValue: e.value })),
      log: (ctx, e) => console.log(ctx.inputValue),
      clear: assign((ctx, e) => ({ inputValue: "" }))
    }
  }
);

function App() {
  const [current, send] = useMachine(simpleFormMachine);
  return (
    <div className="App">
      <h1>Hello World!</h1>
      <h2>Submit to log some text on the console</h2>
      <form
        onSubmit={e => {
          e.preventDefault();
          send("SUBMIT");
          send("STOPPED_TYPING");
        }}
      >
        <input
          type="text"
          placeholder="Enter text"
          value={current.context.inputValue}
          onChange={e =>
            send({
              type: "TYPING",
              value: e.target.value
            })
          }
        />
        <button>submit</button>
      </form>
    </div>
  );
}

...
```

For the full code check out the codeSandbox below: 

{% codesandbox icy-tree-jxe3d %}

See below for the final state chart:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/6fb9alcbv3z48epejkky.PNG)

Did I miss anything? Got a better way of doing this? leave a comment! :]

Ok! Now back to learning 👨🏿‍💻