Learning ReasonReact Step by Step Part: 8
Tags: reason, react, learning, tutorial
Published: 2019-10-10 19:45:08

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

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

---

We [set up our form validation rules, tackled state management and most of the logic](https://www.rockyourcode.com/learning-reason-react-step-by-step-part-7).

Now, we have to hook up our validation logic with the form. Each time a user types into the form, we want to update our validation rules immediately. We can then display the form validation status and give feedback to the user.

The user should only be allowed to submit the form if all rules are valid.

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

The complete code is available on [Github][reasonform].

## Validate

We have a `validate` function in our `useForm` hook, but where do we call it?

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

let useForm = (~formType, ~callback) => {
// previous code

let validate = (~formData=formData, ()) =>
    switch (formType) {
    | "register" =>
      formData.username->UsernameLongEnough |> dispatchRegisterFormRules;
      formData.email->EmailLongEnough |> dispatchRegisterFormRules;
      formData.email->EmailForRegistrationValid |> dispatchRegisterFormRules;
      formData.password->PasswordLongEnough |> dispatchRegisterFormRules;
    | "login" =>
      formData.email->EmailRequired |> dispatchLoginFormRules;
      formData.email->EmailForLoginValid |> dispatchLoginFormRules;
      formData.password->PasswordRequired |> dispatchLoginFormRules;
    | _ => ()
    };

// more code
};

```

**Remember that we have two main pieces of state**: our form data (username, email, password) and validation rules (including the `valid` status).

These two are independent from each other. But we have to **synchronize** them.

To update the validation state depending on the data state, we have to use a trick: [`useEffect`][reactuseeffect].

Every time our form data updates, we let `useEffect` call the `validate` function; thus updating the form validation state.

```js
change in `formData` --> useEffect calls `validate` --> updates `formRules`
```

You can read more about `useEffect` on the [React Docs][reactuseeffect] or Dan Abramov's excellent [A Complete Guide to useEffect][useeffectguide].

Reason's syntax for `useEffect` requires [defining the number of dependencies][reasonhooks]:

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

/* global scope of the module */
React.useEffect1(                 // (A)
    () => {
      validate(~formData, ());    // (B)
      None;                       // (C)
    },
    [|formData|],                 // (A)
  );
```

We handle updates to `formData` inside its own `useReducer` hook - this happens every time a user types into the form.

Now, when `formData` changes, the `useEffect1` hook (`A`) fires off our `validate` function (`B`). The function accepts the current `formData` and will check all of our validation rules. The `useEffect` hook performs a side-effect: we don't need to return anything, thus adding `None` as the return (line `C1`).

As an alternative, you could **[derive state instead of synchronizing it][derivedstate]**.  
That way, you'd only have the `formData` as state, and would compute the validation rules state from `formData`.

## More State

We have to track if _all_ rules are valid (see line `A`) before we allow the user to submit the form.

Instead of using `useReducer`, we can take a simpler approach with `useState`:

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

let useForm = (~formType, ~callback) => {
  // previous code

  let (allValid, setAllValid) = React.useState(() => false);   // (A)

  // more code

}
```

Reason asks you to create the initial state for the React hook with a function: a [lazy initial state](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state).  
Quite cumbersome.

Let's update our `handleSubmit` function:

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

let useForm = (~formType, ~callback) => {
  // previous code

  let handleSubmit = evt => {
      ReactEvent.Form.preventDefault(evt);
      setAllValid(_ => areAllRulesValid(~formRules));
    };

  // more code
  }

```

How do we know if all validation rules are valid? We have to traverse the Array of validation rules and see if they all have a `valid: true` flag. Here is the helper function:

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

/* global scope of the module */
let areAllRulesValid = (~formRules) =>
  Belt.Array.every(formRules, rule => rule.FormTypes.valid); // (A)
```

[Belt.Array](https://bucklescript.github.io/bucklescript/api/Belt.Array.html) offers the familiar JavaScript methods for arrays.  
`Belt.Array.every` is the BuckleScript equivalent of [`array.every`](https://devdocs.io/javascript/global_objects/array/every):

> The every() method tests whether all elements in the array pass the test implemented by the provided function.

Did you see that we have to define the type for a rule (line `A`)?  
This is a shorthand type signature.  
Instead of saying `rule.FormTypes.rules.valid`, we can abbreviate to `rule.FormTypes.valid`. Reason will check the `FormTypes.re` module and find the correct type.

## Submit the Form

Now, if the user submits the form, we check if all rules are valid and toggle the submit status.

As another side-effect, we'll now run the callback function for submitting the form if `allValid` is true. `useForm` received the callback function as an argument:

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

/* inside useForm */
let useForm = (~formType, ~callback) => {
// previous code

 React.useEffect1(
    () =>
      allValid ?
        {
          callback();
          dispatchFormData(ResetState);
          None;
        } :
        None,
    [|allValid|],
  );

  // more code
}

```

You can find the complete code for the custom `useForm` hook on [Github][reasonform].

## Reflections

It took me a long time to get this working.

The biggest stumbling blocks were Reason's **type errors**, making the form work for both "login" and "register" (**code reuse/code duplication**) and **state management with React hooks**.

On the Reason side, it was difficult to set up the logic for the two different type of forms. Reason's type system is very strict. It didn't allow me to use one "container" for both types of forms, so I had to work around that.  
The strict typing eliminates potential bugs, but it's hard to wrap my head around some errors.

On the React side, I had problems porting my mental model of the class-based component life-cycles to React hooks and synchronizing state.

[authenticationcourse]: https://testdriven.io/courses/auth-flask-react/
[reasonstep6]: https://www.rockyourcode.com/learning-reason-react-step-by-step-part-6
[listandarray]: https://reasonml.github.io/docs/en/list-and-array#array
[demo]: https://reason-form.firebaseapp.com
[derivedstate]: https://kentcdodds.com/blog/dont-sync-state-derive-it
[stalestate]: https://johno.com/react-hooks-stale-state
[bsjsre]: https://bucklescript.github.io/docs/en/regular-expression#docsNav
[letbinding]: https://reasonml.github.io/docs/en/let-binding#bindings-are-immutable
[labeled]: https://reasonml.github.io/docs/en/function#labeled-arguments
[reasonform]: https://github.com/sophiabrandt/reason-form/blob/master/src/UseForm.re
[useeffectguide]: https://overreacted.io/a-complete-guide-to-useeffect/
[reactuseeffect]: https://reactjs.org/docs/hooks-effect.html
[reasonhooks]: https://reasonml.github.io/reason-react/docs/en/components#hooks