Learning ReasonReact Step by Step Part: 4
Tags: reason, react, learning, tutorial
Published: 2019-09-17 19:23:34

---
title: Learning ReasonReact Step by Step Part: 4
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-4
---

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

---

So far, we've created [a simple form component with a `useReducer` hook](https://github.com/sophiabrandt/reason-form/blob/e51d7cde835ab56eec4cd98564a59d8dc77e0811/src/Form.re).

This form works with an email and a password - it could be a login form.

But what if we would like to use the same logic to create a register form, too?

## Make The Form Component Re-Usable With a Custom Hook

**We know the shape of our data**: we have three form fields: email, password, and username. We will only show the username field on the register page.  
**But we have to set up a [record](https://reasonml.github.io/docs/en/record) for all of our state**:

```reasonml
/* src/Form.re */
type state = {
  username: string, // *new
  email: string,
  password: string,
};
```

**Let's extract our `useReducer` hook into a separate function** and adjust the actions. First, the initial state of our form, the action type and the reducer function:

```reasonml
/* src/Form.re */
let initialState = {username: "", email: "", password: ""};

type action =
  | SetUsername(string)
  | SetEmail(string)
  | SetPassword(string) // *new
  | ResetState;         // *new

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

In our [last attempt](https://www.rockyourcode.com/learning-reason-react-step-by-step-part-3) we used `useReducer` inside the component, and also hooked up the dispatch functions inside the component's JSX.

```reasonml
/* src/Form.re */
[@react.component]
let make = () => {
  let initialState = {email: "", password: ""};

  let (state, dispatch) = React.useReducer(reducer,initialState);

  // ...

    <input
        className="input"
        type_="email"
        name="email"
        value={state.email}
        required=true
        onChange={evt => valueFromEvent(evt)->SetEmail |> dispatch}
      />

 // ...

```

Instead, I want to **create a custom hook that deals with the form actions and with handling state**.

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

  let (state, dispatch) = React.useReducer(reducer, initialState);

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

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

  (state, handleChange, handleSubmit); // (D)
};
```

The custom hook takes a callback function (`A`) that we'll use when we submit the form. Now different forms could add different logic!

The `handleChange` function mirrors what we had before. We use **[pattern-matching][patternmatching]** on each action. All actions deal with the state of the form: they update it or reset it.

What's all this `nameFromEvent` and `valueFromEvent` stuff?

We have to somehow interact with the DOM - in JavaScript, it would be `evt.target.value` and `evt.target.name`.

For example, if the target name is "password," then update the password state with the value we got out of the HTML form.

But wait! The action `variant` also has the option to reset a form. We don't want to handle this case in `handleChange`. Instead, we dispatch it (see on line `C`: `ResetState`) when we submit the form.

**Our pattern-matching in `handleChange` isn't exhaustive.** We don't handle all possible cases.  
 That's why we have to set up a "catch-all" case in line A. The underscore matches on everything. We don't want to return anything, so we return the `Unit` type (a type that represents "no value") - a.k.a. empty brackets (see `line B`).

**In the end, we have to return `state`, `handleChange`, and `handleSubmit`** (`D`), so that we can use it in our form component as a custom hook.

## Use The Custom Hook In The Form Component

Now, let's take advantage of our custom hook inside the React component:

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

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

  //...

```

The `logger` function is our callback for `useForm`. Then we de-structure `state`, `handleChange`, and `handleSubmit` from `useForm`.

Our component will take a prop called `formType`. The `formType` will tell us if it's the Register page or the Login Page.

For example, in `src/App.re` it would look like this:

```reason
[@react.component]
let make = () => <Form formType="login"/>;
```

Now, we'll have to add the logic to the JSX:

```reasonml
// ...

<div className="section is-fullheight">
    <div className="container">
      <div className="column is-4 is-offset-4">
        <h1 className="is-size-1 has-text-centered is-capitalized">
          {formType |> str}   // (A)
        </h1>
        <br />
        <div className="box">
          <form onSubmit=handleSubmit>      // (B)
            {
              formType === "register" ?     // (C)
                <div className="field">
                  <label className="label"> {"Username" |> str} </label>
                  <div className="control">
                    <input
                      className="input"
                      type_="text"
                      name="username"
                      value={state.username}
                      required=true
                      onChange=handleChange  // (D)
                    />
                  </div>
                </div> :
                ReasonReact.null
            }
            <div className="field">
              <label className="label"> {"Email Address" |> str} </label>
              <div className="control">
                <input
                  className="input"
                  type_="email"
                  name="email"
                  value={state.email}
                  required=true
                  onChange=handleChange   // (D)
                />
              </div>
            </div>
            <div className="field">
              <label className="label"> {"Password" |> str} </label>
              <div className="control">
                <input
                  className="input"
                  type_="password"
                  name="password"
                  value={state.password}
                  required=true
                  onChange=handleChange // (D)
                />
              </div>
            </div>
            <button
              type_="submit"
              className="button is-block is-info is-fullwidth is-uppercase">
              {formType |> str} // (A)
              <br />
            </button>
          </form>
        </div>
      </div>
    </div>
  </div>;
```

On lines A we can see that the form will display a heading or a button text depending on the `formType` props.

Line B shows how we submit a form with the custom `useForm` function `handleSubmit`.

Line C shows how we conditionally display the username fields, if our form is the register form (`formType` is the props we get from the main `App.re`).  
When we don't want to render the fields, we have to pass `ReasonReact.null`.  
In JavaScript, you can do a boolean render like so:
```js
(formType === "register" && (<JSX here>)
```
That's discouraged in ReasonML. You have to be explicit about what happens if you don't meet the condition.

Lines D show that we have to pass down the `handleChange` function to each `onChange` input field as well. Our `useForm` custom hook encapsulates the logic on how to handle state inside the `useForm` hook. That makes it easier to understand our code.

## Code Repository

The complete Form module is available on [GitHub](https://github.com/sophiabrandt/reason-form/blob/4d9e5093f43c047d4c807adff1849f5e1281cbba/src/Form.re).

## Thoughts

After some initial hiccups, it's **surprisingly straightforward to write ReasonReact**.

ReasonReact keeps close to React.js.  
You can "think in React.js" and port it over to ReasonReact/ReasonML. The new JSX syntax (released earlier this year) also almost feels like native React.js.

Sometimes the similarities are almost a detriment, as they hide that Reason and JavaScript _are_ different languages after all.

**Pattern-matching is one of the killer features of Reason.** I came to enjoy it when learning Elixir, and I am happy to use it on the front-end with ReasonReact now, too.

### Further Reading

- [ReasonReact][reasonreact]
- [A ReasonReact Tutorial][jaredforsythe]
- [Using Custom React Hooks to Simplify Forms][upmostly] by James King

[reasonreact]: https://reasonml.github.io/reason-react/en/
[upmostly]: https://upmostly.com/tutorials/using-custom-react-hooks-simplify-forms
[jaredforsythe]: https://jaredforsyth.com/posts/a-reason-react-tutorial/
[patternmatching]: https://reasonml.github.io/docs/en/pattern-matching#docsNav