Learning ReasonReact Step by Step Part: 6
Tags: reason, react, learning, tutorial
Published: 2019-10-05 04:36:52

---
title: Learning ReasonReact Step by Step Part: 6
published: true
description: Learning how ReasonReact works from a beginner's perspective
tags: reasonml, react, learning, tutorial
series: Learning ReasonReact Step by Step
canonical_url: https://www.rockyourcode.com/learning-reason-react-step-by-step-part-6
---

**UPDATE**:  
ReasonML + BuckleScript is now [Rescript](https://rescript-lang.org/blog/bucklescript-is-rebranding).  
As the ecosystem has changed around those tools, this blog post is not accurate anymore.

---

## What We've Done So Far

So far, we've created a **[simple ReasonReact form with a custom useForm hook][basic-form]** and client-side routing.

The custom hook uses a `useReducer` to handle state and transitions. The state is a [Reason Record][record], a type-safe immutable "object."  
We first define a type for this record, then we create the initial state:

```reasonml
type state = {
  username: string,
  email: string,
  password: string,
};

let initialState = {username: "", email: "", password: ""};
```

The code for our custom hook looks very similar to JavaScript, but it uses some nice ReasonML features like [pattern-matching][patternmatching].

For example, the actions and the reducer:

```reasonml
type action =
  | SetUsername(string)
  | SetEmail(string)
  | SetPassword(string)
  | ResetState;

let reducer = (state, action) =>
  switch (action) {
  | SetUsername(username) => {...state, username}
  | SetEmail(email) => {...state, email}
  | SetPassword(password) => {...state, password}
  | ResetState => initialState
  };
```

We're also making sure that our form targets are typed. The form events have to be strings:

```reasonml
let valueFromEvent = evt: string => evt->ReactEvent.Form.target##value;
let nameFromEvent = evt: string => evt->ReactEvent.Form.target##name;
```

While we handle the change in the forms inside `useForm`, we defer what to do when we submit a form to our main component.  
`useForm` takes a callback function as an argument. We then define it in the Form component:

```reasonml
/* main form component */
[@react.component]
let make = (~formType) => {
  let logger = () => Js.log("Form submitted");

  let (state, handleChange, handleSubmit) = useForm(~callback=logger);

  // JSX here
};
```

Our **ReasonReact code feels similar to JavaScript React code**, but it's type-safe. During development, we profited from the **fast compiler**, which caught our type errors.

**There are some pitfalls and ugly warts, and some fundamental differences.**  
For example, we have to write our bindings to the events of input fields.  
Every time we display some text within JSX, we have to tell Reason that the text is a string.  
We can't use a dynamic object as a container for our form values. We had to set up a type beforehand, and thus are limited to using it as the structure for our form values.

Nonetheless, I wager that a React developer could read the code and be able to understand what's happening here.

## Form Validation

Let's make our example app a bit more challenging and add some validation rules.

I'll admit that it took me quite some time to get it right.

The goal is to extend the custom `useForm` hook to check for valid input and to display the validation status directly after the user has typed into a form field.

**☞ [View Demo][demo]**

### Extract to Different Modules

We'll create a different module for `useForm`. Thus, it's also better to extract our type definitions, because we have to reference them both in the file `Form.re` as well as in `UseForm.re`.

Create `src/FormTypes.re`:

```reasonml
type formState = {   // (A)
  username: string,
  email: string,
  password: string,
};

type formRule = {  // (B)
  id: int,
  field: string,
  message: string,
  valid: bool,
};

type formRules = array(formRule); // (C)

```

Line A is copied `Form.re`. But we'll rename the type from `state` to `formState` to be more explicit.

Then we set up the type for our validation rules (`B`). We will hold all rules in an array (line `C`).  
We will have a fixed number of rules, and we will have to map over all of them. Arrays are a good fit because they have a fixed size. We could use a List, which is an immutable singly-linked list under the hood. But finding an element is O(n) for both Lists and Arrays, and Arrays are a better fit for sets of items of known size.  
The [Ocaml website offers a concise overview of the different standard containers][ocamlcontainers].

### React Hook Bugs

**At this stage, I originally made a grave mistake.**  
Records are **immutable by default** in ReasonML. But you [_can_ create mutable record fields](https://reasonml.github.io/docs/en/record#mutable-update).

At first, I had a mutable `valid` field:

```reasonml
type formRule = {
  id: int,
  field: string,
  message: string,
  mutable valid: bool, // Look, Ma: mutable record field!
};

```

The idea was to directly toggle the valid state in my form validation check. If the input of a field meets the condition, I will directly target that rule in my array like so:

```reasonml
/* inside UseForm.re */
let registerFormRules: FormTypes.formRules = [|    // ReasonML syntax for Arrays: [||]
  {                                                // the syntax for Lists is: []
    id: 0,
    field: "username",
    message: "Username must have at least 5 characters.",
    valid: false,
  },
// more rules
|];


let registerFormRulesReducer =
    (state: FormTypes.formRules, action: registerFormRulesAction) =>
  switch (action) {
  | UsernameLongEnough(username) =>
    username |> String.length >= 5 ?
      {
        state[0].valid = true;     // if the username field has at least 5 characters, toggle
        state;                     // the valid field to true (mutable update)
      } :
      {
        state[0].valid = false;
        state;
      }
  // more form rule checks
  };
```

I can access a rule via the Array index which is constant time (O(1)). I don't have to map over the complete data structure to target the rule I want to change.

**But this approach created a nasty bug!**  
And this wasn't due to Reason, but to a misconception of React hooks.  
I [wrote about this on Thursday](https://www.rockyourcode.com/beginner-error-with-reason-react-and-updating-state): **don't mutate state directly** - even if you do it with `useReducer`.  
[You have to clone the Array, change it, and then pass a new Array to `setState` (or dispatch a new state with `useReducer`)][stalestate].

Don't use a mutable record if you work with React hooks!

### Refactor Form.re/UseForm.re

Extract `useForm` from `Form.re` into a new file. Let's also rename the values and functions to be more explicit.

```reasonml
/* src/UseForm.re */

let initialFormData: FormTypes.formState = { // (A)
  username: "",
  email: "",
  password: "",
};

type formAction =
  | SetUsername(string)
  | SetEmail(string)
  | SetPassword(string)
  | ResetState;

let formReducer = (state: FormTypes.formState, action: formAction) => // (A)
  switch (action) {
  | SetUsername(username) => {...state, username}
  | SetEmail(email) => {...state, email}
  | SetPassword(password) => {...state, password}
  | ResetState => initialState
  };

let useForm = (~formType, ~callback) => {  // (B)
  let valueFromEvent = evt: string => evt->ReactEvent.Form.target##value;
  let nameFromEvent = evt: string => evt->ReactEvent.Form.target##name;

  let (formData, dispatchFormData) =
    React.useReducer(formReducer, initialFormData);

  let handleChange = evt => {
    ReactEvent.Form.persist(evt);
    switch (nameFromEvent(evt)) {
    | "username" => valueFromEvent(evt)->SetUsername |> dispatchFormData
    | "email" => valueFromEvent(evt)->SetEmail |> dispatchFormData
    | "password" => valueFromEvent(evt)->SetPassword |> dispatchFormData
    | _ => ()
    };
  };

  let handleSubmit = evt => {
    ReactEvent.Form.preventDefault(evt);
    callback();
    dispatch(ResetState);
  };

  (formData, handleChange, handleSubmit);
};
```

We now have to reference the type information from a different module (lines `A`).

Additionally, we will need to tell `useForm` the form type: "register" or "login." There will be different rules for these two forms, so we'll need to differentiate between them.

`Form.re` now needs to use the correct `useForm`:

```reasonml
/* inside Form.re */
[@react.component]
let make = (~formType) => {
  let logger = () => Js.log("Form submitted");

  let (state, handleChange, handleSubmit) =
    UseForm.useForm(~formType, ~callback=logger);    // (A)

  // JSX here
};
```

We reference the function now with its module name and pass down the `formType` props (line `A`).

So far, so good. Everything should work as before, and we're now in good shape to add our validation logic.

### Further Reading

- [React Hooks and stale state][stalestate] by John Otander

[basic-form]: https://github.com/sophiabrandt/reason-form/blob/finished-basic-form-with-dev-routing/src/Form.re
[record]: https://reasonml.github.io/docs/en/record
[patternmatching]: https://reasonml.github.io/docs/en/pattern-matching#docsNav
[ocamlcontainers]: https://ocaml.org/learn/tutorials/comparison_of_standard_containers.html
[stalestate]: https://johno.com/react-hooks-stale-state
[demo]: https://reason-form.firebaseapp.com