ReasonML + BuckleScript is now Rescript.
As the ecosystem has changed around those tools, this blog post is not accurate anymore.

In the last post, we finally finished the custom useForm hook.
You can see the code on GitHub.

Use The Custom Hook

Let’s switch to our main form component: scr/Form.re.

We need to connect the component to our custom hook.

/* src/Form.re */

let make = (~formType) => {
  let logger = () => Js.log("Form submitted");

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

    // JSX here

So far, so good.

Display Form Validation Rules

Now let’s create a new component called FormErrors, which will be in charge of displaying the list of form validation rules and their status.

View Demo

We’ll create a nested module. The Reason Documentation recommends a flat project structure:

Try not to have too many nested folders. Keep your project flat, and have fewer files (reminder: you can use nested modules).

Inside src/Form.re:

let str = ReasonReact.string;

module FormErrors = {
  let make = (~formRules: FormTypes.formRules) => // (A)
            rule =>
                key={rule.FormTypes.id |> string_of_int} // (B)
                  rule.valid ?
                    "is-success help is-size-6" : "is-danger help is-size-6"
                <i className={rule.valid ? "fas fa-check" : "fas fa-times"} />
                {" " |> str}
                {rule.FormTypes.message |> str} // (B)
          |> React.array

The make function takes a labeled argument with the type FormTypes.formRules (A). The formRules are defined in src/FormTypes.re and we can access them with the dot operator.

We use Array.map, a native Reason function, to loop over the Array. Unfortunately, it takes the input function as the first argument the array as the second argument.

Now the type checker doesn’t know the type of each rule. That’s why we have to tell Reason the type again (see lines B).

As an alternative, you could use BuckleScript’s Belt library, which offers a more familiar syntax for JavaScript developers. Belt.Array.map takes the array as a first argument, and the function as the second argument.

We also have to convert types (B) and convert the Array to a React.array. Reason’s type system is strict, and you have to jump through some hoops to get everything working.

Other than that, the component looks almost the same as a React component.

Connect Form And FormErrors

We now have to show the FormErrors component inside the Form component - the same as in normal React.

/* src/Form.re */

let make = (~formType) => {
  // form logic
 let (state, formRules, handleChange, handleSubmit) = // (A)
    UseForm.useForm(~formType, ~callback=logger);

<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}
        <br />
          switch (formRules) {         // (B)
          | [||] => ReasonReact.null
          | _ => <FormErrors formRules />
        <br />
        // more JSX

The above code shows how we conditionally display the FormErrors component (B).

If we don’t have any formRules we display ReasonReact.null: we show nothing.

I’m sure you’ve previously run into the error that the array is undefined, and the map function can’t run.
We avoid this error by always initializing an array with validation rules. It’s either an empty array or an array containing login or registration rules.

If we have an array with rules, display FormErrors, and hand down the formRules that we received from the custom useForm hook (A).


And that’s the complete example. You can find the code on Github. I deployed the live demo to Firebase.

I originally wanted to deploy the demo to GitHub Pages but ran into client routing problems.

I’ll write a recap and thoughts about my learning process in a later post.