ReasonML for production React Apps? 🤔 (Part 1)
Tags: reason, react, javascript
Published: 2019-04-19 12:49:07

---
title: ReasonML for production React Apps? 🤔 (Part 1)
series: ReasonML for production React Apps? 🤔
published: true
description: 
tags: ReasonML, React, JavaScript
canonical_url: https://tinloof.com/blog/reasonml-for-production-react-apps-part-1/
---

ReasonML is a functional programming language with smartly inferred strict types, that compiles to JavaScript. ReasonReact is Reason bindings for ReactJS (aka the translated ReasonML version of the ReactJS). It has improved a lot lately and even added support for hooks in a release a couple of days ago.

In this series of articles, I will build applications in ReasonReact and try to accomplish most of the tasks I usually do with ReactJS. For each article, I will share what I like/dislike about building React applications in Reason. The goal is to determine how ready is ReasonML for building serious React applications.

# What are we going to build?
I decided to start with a simple application. We will build a small words counter with the following features:
- There is an input where I can write text.
- There is a word count that updates while I write text.
- There is a button to clear text.
- There is a button to copy text.


<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/1rl3gdneih3awmreato1.gif" alt="Final result">
  <figcaption>Final result</figcaption>
</figure>

You can find the final source code [here](https://github.com/siffogh/words-counter). Since we will build the application in iterations, there is a branch for each iteration.


# Setting up the project & editor
First, let's download the Reason to JavaScript compiler [bs-platform](https://bucklescript.github.io/) (BuckleScript):

```sh
npm install -g bs-platform
```

The package comes with _bsb_, a CLI tool to quickly bootstrap a Reason project based on a template.
Let's generate our project based on the react-hooks template:

```sh
bsb -init words-counter -theme react-hooks
```

Let's also use VSCode as our code editor, and download [reason-vscode](https://marketplace.visualstudio.com/items?itemName=jaredly.reason-vscode). This is the editor plugin officially recommended by [ReasonML](https://reasonml.github.io/docs/en/editor-plugins).

To take advantage of the formatting feature, let's enable the _Format on Save_ option in the editor's settings:

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/71vo3wmgs1e9revyjyza.png" alt="Enabling the Format On Save option on VSCode">
  <figcaption>Enabling the Format On Save option on VSCode</figcaption>
</figure>

## I like 👍

- The getting-started experience is very good. The BuckleScript build tool (bsb) is a much faster version of _create-react-app_ or _yeoman_.

- The Editor tooling is also great: 
  - It formats the code style and syntax (just like configuring ESLint with Prettier).
  - It also provides information about types when hovering on values.

# Iteration #1: there is an input where I can write text
In this first iteration, we just want to have a nice text area with a title to write text and store it in a state variable:

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/pi8g9lchiksny3xymgxi.gif" alt="Iteration #1: there is an input where I can write text">
  <figcaption>Iteration #1: there is an input where I can write text</figcaption>
</figure>


```ocaml
/* src/App.re */

[%bs.raw {|require('./App.css')|}];

[@react.component]
let make = () => {
  let (text, setText) = React.useState(() => "");

  let handleTextChange = e => ReactEvent.Form.target(e)##value |> setText;

  <div className="App">
    <div className="header">
      <h3> {"Words Counter" |> ReasonReact.string} </h3>
    </div>
    <textarea
      placeholder="Express yourself..."
      value=text
      onChange=handleTextChange
    />
  </div>;
};
```

## I dislike 👎
- Accessing the target value of a form event is a bit of overhead.
- Having to use `ReasonReact.string` with every `string` value needs some getting used to, even if the composition operator `|>` helps a bit.
- `useState` requires a function. Although this is useful when making an expensive initial state computation, it's unnecessary in most cases. I would have preferred having the 2 forms of this hook (one that accepts a value, and one that accepts a function) with different names.

## I like 👍
- It was pretty easy to get started with a simple app with CSS. Although the syntax for requiring a CSS file is a bit weird, the whole experience is still great.

- DOM elements are fully typed, which has 2 benefits:
 - You can know before runtime whether you assigned a wrong value to a prop: no more typos! It's like having propTypes built-in for the attributes of all the DOM elements.
 - DOM elements are self-documenting. You can instantly hover on an element to see the possible attributes it accepts (no need to Google them anymore).

## Iteration #2: there is a word count that updates while I write text

In this iteration, we want to show a count of the words typed so far:

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/v5jniuicgm8jucye597k.gif" alt="Iteration #2: there is a word count that updates while I write text">
  <figcaption>Iteration #2: there is a word count that updates while I write text</figcaption>
</figure>

First, let's create a function that returns the number of words in a string input:

```ocaml
let countWordsInString = text => {
  let spacesRegex = Js.Re.fromString("\s+");

  switch (text) {
  | "" => 0
  | noneEmptyText =>
    noneEmptyText
    |> Js.String.trim
    |> Js.String.splitByRe(spacesRegex)
    |> Js.Array.length
  };
};
```

So here's what the function does:
- If the text is empty, we just return 0.
- Otherwise, we just trim the text and use `Js.String.splitByRe` to split it by the regular expression `\s+` (which basically means 1 or more spaces followed by any character) and return the length of the array we obtain.


```ocaml
/* src/App.re */

[%bs.raw {|require('./App.css')|}];

let countWordsInString = text => {
  let spacesRegex = Js.Re.fromString("\s+");

  switch (text) {
  | "" => 0
  | noneEmptyText =>
    noneEmptyText
    |> Js.String.trim
    |> Js.String.splitByRe(spacesRegex)
    |> Js.Array.length
  };
};

[@react.component]
let make = () => {
  let (text, setText) = React.useState(() => "");

  let handleTextChange = e => ReactEvent.Form.target(e)##value |> setText;

  let wordsCountText =
    (text |> countWordsInString |> string_of_int) ++ " words";

  <div className="App">
    <div className="header">
      <h3> {"Words Counter" |> ReasonReact.string} </h3>
      <span> {ReasonReact.string(wordsCountText)} </span>
    </div>
    <textarea
      placeholder="Express yourself..."
      value=text
      onChange=handleTextChange
    />
  </div>;
};
```

## I like 👍
- Reason's smart inference is great:
  - Although I didn't provide any type annotations, the `countWordsInString` function is self-documenting. Hovering over it shows that it accepts a `string` and returns an `int`.
  - At some point, I returned the split array from `countWordsInString` instead of its length. I was able to catch that bug at build time before even looking at the application in the browser.

## Iteration #3: there is a button to clear text
In this iteration, we want to have a button to clear text:

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/hbmtb7lvggsrvvlklbmh.gif" alt="Iteration #3: there is a button to clear textLet's add a button to clear the text in the textarea.">
  <figcaption>Iteration #3: there is a button to clear textLet's add a button to clear the text in the textarea.</figcaption>
</figure>

In JavaScript, I use the [svgr](https://github.com/smooth-code/svgr) Webpack loader to import SVG icons as React components directly from their corresponding `.svg` files. 


Since imports are typed in Reason, I decided to have an icon in the clear button to see how painful it would be to import SVG icons as React components.

Since we will have another button in the next iteration which will look differently (spoiler alert), let's have our button as a separate component and make it have two categories for styling purposes:
- PRIMARY: blue button
- SECONDARY: gray button

```ocaml
/* src/Button.re */

[%bs.raw {|require('./Button.css')|}];

type categoryT =
  | SECONDARY
  | PRIMARY;

let classNameOfCategory = category =>
  "Button "
  ++ (
    switch (category) {
    | SECONDARY => "secondary"
    | PRIMARY => "primary"
    }
  );

[@react.component]
let make =
    (
      ~onClick,
      ~title: string,
      ~children: ReasonReact.reactElement,
      ~disabled=false,
      ~category=SECONDARY,
    ) => {
  <button onClick className={category |> classNameOfCategory} title disabled>
    children
  </button>;
};
```

To use svgr, let's add the following rule in the Webpack `module` configuration:

```
{
  test: /\.svg$/,
  use: ['@svgr/webpack'],
}
```

In JavaScript, we can import an svg component by doing this:

```js
import {ReactComponent as Times} from './times';
```

Since Webpack applies svgr to the JavaScript resulting from compiling our Reason source code, we just need to make BuckleScript translate our Reason import into a named es6 import.

To do so, we first have to configure `/bs-config.json` (the configuration file for the BuckleScript compiler) to use es6 imports:

```json
  "package-specs": [
    {
      "module": "es6",
      "in-source": true
    }
  ],
```

ReasonReact `make` function compiles to a JavaScript React component! This means that if we want to use a component "Foo" that is written in JavaScript, all that we have to do is:
1- Create the component in Reason.
2- Import the JS component as the `make` function of the Reason component and annotate its props.

So in the module `Foo.re`, we would have the following:

```ocaml
[@bs.module "./path/to/Foo.js"][@react.component]
external make: (~someProp: string, ~someOtherProp: int) => React.element = "default";
```

Which means ... that we can use that to import an SVG component with svgr!
Let's use it to import the `./times.svg` icon and just annotate the `height` prop since it's the only one we will be using:

```ocaml
[@bs.module "./times.svg"] [@react.component]
external make: (~height: string) => React.element = "default";
```

Our ReasonReact components were automatically considered as modules because we created them in separate files (Button.re, App.re). Since the Times component is pretty small (2 lines), we can use Reason's module syntax to create it:

```ocaml
/* src/App.re */

[%bs.raw {|require('./App.css')|}];

let countWordsInString = text => {
  let spacesRegex = Js.Re.fromString("\s+");

  switch (text) {
  | "" => 0
  | noneEmptyText =>
    noneEmptyText
    |> Js.String.trim
    |> Js.String.splitByRe(spacesRegex)
    |> Js.Array.length
  };
};

module Times = {
  [@bs.module "./times.svg"] [@react.component]
  external make: (~height: string) => React.element = "default";
};

[@react.component]
let make = () => {
  let (text, setText) = React.useState(() => "");

  let handleTextChange = e => ReactEvent.Form.target(e)##value |> setText;

  let handleClearClick = _ => setText(_ => "");

  let wordsCountText =
    (text |> countWordsInString |> string_of_int) ++ " words";

  <div className="App">
    <div className="header">
      <h3> {"Words Counter" |> ReasonReact.string} </h3>
      <span> {ReasonReact.string(wordsCountText)} </span>
    </div>
    <textarea
      placeholder="Express yourself..."
      value=text
      onChange=handleTextChange
    />
    <div className="footer">
      <Button
        title="Clear text"
        onClick=handleClearClick
        disabled={String.length(text) === 0}>
        <Times height="20px" />
      </Button>
    </div>
  </div>;
};
```


## I dislike 👎
If I want to make a reusable button that should accept all the attributes a native DOM button does, I would have to list all of those attributes. In JavaScript, I can avoid that by just using the spread operation:

```js
function Button(props) {
    return <button {...props} />
}
```
However, ReasonReact doesn't allow the spread operator. (I wonder if there is a way to achieve what I want with ReasonReact 🤔)

## I like 👍
- The ability to specify the type of children is very powerful. This is possible with PropTypes in JavaScript but very limited compared to Reason. We can, for example, specify that the component only accepts 2 children (as a tuple).
- Variants were useful to categorize buttons. Categorizing components is something that occurs very often, so being able to do that with an actual reliable type instead of string constants is a huge win. 
- Using the Webpack svgr plugin to import an SVG as a component was actually pretty painless. It's very simple and yet ensures type safety since we have to annotate the types.

## Iteration #4: there is a button to copy text
In this iteration, we want to have a button to copy text to the clipboard:

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/1rl3gdneih3awmreato1.gif" alt="Iteration #4: there is a button to copy text">
  <figcaption>Iteration #4: there is a button to copy text</figcaption>
</figure>

To do so, I want to use [react-copy-to-clipboard](https://www.npmjs.com/package/react-copy-to-clipboard), which is a React component library that allows copying text to the clipboard very easily. Since it's a JavaScript library, we can use the same import approach we used in the previous iteration. The only difference is that we will make a named import and not a default import.

```ocaml
/* src/App.re */

[%bs.raw {|require('./App.css')|}];

let countWordsInString = text => {
  let spacesRegex = Js.Re.fromString("\s+");

  switch (text) {
  | "" => 0
  | noneEmptyText =>
    noneEmptyText
    |> Js.String.trim
    |> Js.String.splitByRe(spacesRegex)
    |> Js.Array.length
  };
};

module Times = {
  [@bs.module "./icons/times.svg"] [@react.component]
  external make: (~height: string) => React.element = "default";
};

module Copy = {
  [@bs.module "./icons/copy.svg"] [@react.component]
  external make: (~height: string) => React.element = "default";
};

module CopyClipboard = {
  [@bs.module "react-copy-to-clipboard"] [@react.component]
  external make: (~text: string, ~children: React.element) => React.element =
    "CopyToClipboard";
};

[@react.component]
let make = () => {
  let (text, setText) = React.useState(() => "");

  let handleTextChange = e => ReactEvent.Form.target(e)##value |> setText;

  let handleClearClick = _ => setText(_ => "");

  let wordsCountText =
    (text |> countWordsInString |> string_of_int) ++ " words";

  <div className="App">
    <div className="header">
      <h3> {"Words Counter" |> ReasonReact.string} </h3>
      <span> {ReasonReact.string(wordsCountText)} </span>
    </div>
    <textarea
      placeholder="Express yourself..."
      value=text
      onChange=handleTextChange
    />
    <div className="footer">
      <Button
        title="Clear text"
        onClick=handleClearClick
        disabled={String.length(text) === 0}>
        <Times height="20px" />
      </Button>
      <CopyClipboard text>
        <Button
          title="Copy text"
          disabled={String.length(text) === 0}
          category=Button.PRIMARY>
          <Copy height="20px" />
        </Button>
      </CopyClipboard>
    </div>
  </div>;
};
```

## I like 👍
Importing a JavaScript React component library is also very simple and ensures type safety.