ReasonReact - migration guide to JSX 3 and Hooks
Tags: reason, react, migration, jsx
Published: 2019-10-28 00:25:34

A couple of months ago React bindings for ReasonML language - commonly known as ReasonReact - has got [support for Hooks API](https://reasonml.github.io/reason-react/blog/2019/04/10/react-hooks). With that update, a new version of JSX syntax came as well. We can now write our components in a completely new way! But how to get there, how to rewrite existing components gradually and without pain? Let's dive in!

# The start

To keep things simple, let's assume we want to rewrite a single stateful component - a classical counter with two buttons. The only not usual thing about this component is that it displays the counter value using a function passed as children. It also shows a progress bar by simply printing dots in number equal to the counter value. You'll see later why those two things are interesting.

```reasonml
type action =
  | Dec
  | Inc;

let reducer = (action, state) =>
  switch (action) {
  | Dec => ReasonReact.Update(state - 1)
  | Inc => ReasonReact.Update(state + 1)
  };

let component = ReasonReact.reducerComponent("Counter");

let make = (~initialValue=0, children) => {
  ...component,
  initialState: () => initialValue,
  reducer,
  render: self => {
    let progress = Array.make(self.state, ReasonReact.string("."));
    <>
      <button type_="button" onClick={_ => self.send(Dec)}>
        {"-" |> ReasonReact.string}
      </button>
      {children(self.state)}
      <button type_="button" onClick={_ => self.send(Inc)}>
        {"+" |> ReasonReact.string}
      </button>
      <pre> ...progress </pre>
    </>;
  },
};

```
The usage of this component may look like this.

```reasonml
<Counter initialValue=32>
    ...{num =>
      " The world will end in "
      ++ string_of_int(num)
      ++ " years "
      |> ReasonReact.string
    }
</Counter>;
```

The component doesn't look impressive but is complex enough to present the most important concepts.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/b4e0gqhwp8801hxnlmwq.png)

# Migration script

ReactReason creators know very well how hard the migration of big codebases may be. That's why they provided [migration scripts](https://github.com/chenglou/upgrade-reason-react#installation) for each minor update of the library. This is the best possible starting point.

First, install the script (it will take a while, coffee time!)

```bash
npm i --no-save https://github.com/chenglou/upgrade-reason-react\#0.6.0-to-0.7.0
```

Then, execute it with a path to the file as an argument.


```bash
./node_modules/upgrade-reason-react/lib/bs/bytecode/migrate.byte Counter.re
```

Besides minor replacements, like `{}` with `()` and `ReasonReact.` with `React.`, the only thing that the script does is adding quite a big compatibility layer at the end of the file.

```reasonml
let make =
  ReasonReactCompat.wrapReasonReactForReact(
    ~component,
    (
      reactProps: {
        .
        "initialValue": option('initialValue),
        "children": 'children,
      },
    ) =>
    make(~initialValue=?reactProps##initialValue, reactProps##children)
  );
[@bs.obj]
external makeProps:
  (~children: 'children, ~initialValue: 'initialValue=?, unit) =>
  {
    .
    "initialValue": option('initialValue),
    "children": 'children,
  } =
  "";

```

The snippet allows using old-fashioned components in JSX 3 syntax.

```reasonml
<Counter initialValue=32>
  {num =>
     " The world will end in "
     ++ string_of_int(num)
     ++ " years "
     |> React.string}
</Counter>
```

You may notice that three little dots before curly braces surrounding children function are missing. In JSX 3 everything is single `React.element`, there are no arrays anymore, so we don't need spread. This is the only visible change on the call site.

We're still far from having new JSX syntax in our component. Why to even bother with the script, then? It's useless in such small examples like this one, indeed, but in larger codebases, it may be the only way to start thinking about migration. The compatibility layer makes possible to rewrite components one by one while keeping the project compiling during the process. There is nothing worse than a huge refactor which turns everything upside down and blocks the development of business features and bugfixes for long days or weeks.

Moreover, this technique allows using in the new syntax external dependencies that expose JSX 2 interface only. Everything you have to do is to create a local module that wraps the external one with a snippet similar to one created by the migration script. Then, change all references to the original component, and voilá! You've made the library compatible with JSX 3. You can even make the pull request to the project repository, I bet maintainers will be more than grateful for such a contribution.

# Going further

We're just in half of our journey. Let's make our hands dirty and write some code! To make the `Counter` component closer to the JSX 3 syntax, first of all, remove `component` record and use the new `@react.component` decorator instead. Then, make `children` a labeled argument by adding a little `~` to its name (notice, that's the way this parameter is declared in `makeProps` function of compatibility layer). Finally, remove spread (`...`) from the place where `progress` name is used and, additionally, pass it to `React.array` function. If you inspect the function, you’ll see that it creates single `React.element` from an array of these. That's why we don't need a spread anymore.


```reasonml
[@react.component]
let make = (~initialValue=0, ~children) => {
  let progress = Array.make(self.state, React.string("."));
  <>
    <button type_="button" onClick=(_ => self.send(Dec))>
      ("-" |> React.string)
    </button>
    (children(self.state))
    <button type_="button" onClick=(_ => self.send(Inc))>
      ("+" |> React.string)
    </button>
    <pre> (progress |> React.array) </pre>
  </>;
};
```

The code doesn't compile yet because of the `self` object is no more defined. JSX 3 components are just pure functions, there is no context shared between renders. In these circumstances, where to store the state, how to tie reducer to the component? The answer is...

# Hooks

The newest ReasonReact syntax is almost equal to the JavaScript counterpart. With the last update, we've got not only JSX 3 but also hooks like `useState`, `useEffect` and `useReducer`. The latter is the one that we may use to accomplish the same effect as with `reducerComponent`. There are two changes necessary to the reducer function itself: inverting arguments order (`state` first, `action` last), and removing `ReasonReact.Update` wrapper from the returned value. In opposite to the `reducerComponent`, hooked reducer always returns the next state. If you need to perform side effects, the `useEffect` hook is here to serve you.


```reasonml
type action =
  | Dec
  | Inc;

let reducer = (state, action) =>
  switch (action) {
  | Dec => state - 1
  | Inc => state + 1
  };

[@react.component]
let make = (~initialValue=0, children) => {
  let (state, send) = React.useReducer(reducer, initialValue);

  let progress = Array.make(state, React.string("."));
  <>
    <button type_="button" onClick=(_ => send(Dec))>
```

And... that's it! Now we can just remove the compatibility layer added by the migration script and enjoy the component written with JSX 3 and hooks! The benefits are far less of boilerplate code, the consistent syntax for children (no more dots!) and architecture more similar to one known from JavaScript.

# Final words

Migration from JSX 2 to JSX 3 doesn't need to be painful. Thanks to the script prepared by the ReasonReact team, it's quite easy to accomplish even in large codebases. Recently I did it in [`webpack-stats-explorer`](https://github.com/erykpiast/webpack-stats-explorer/pull/172), the tool for analyzing Webpack bundle stats and comparing them between builds. It's rather a medium-size project but it took me a few evenings, spent mostly on wandering around and wondering what should I do next. In this article, I've compiled knowledge from different documentation pages and community forums, trying to produce clear and transparent migration guide. I hope your experience will be much better thanks to this. Good luck!


#### Sources

6. [JSX 3 and Hooks announcment](https://reasonml.github.io/reason-react/blog/2019/04/10/react-hooks)
6. [Hooks & co. docs](https://reasonml.github.io/reason-react/docs/en/components)
6. [JSX 3 docs](https://reasonml.github.io/reason-react/docs/en/jsx)
6. [Migration scripts repository](https://github.com/chenglou/upgrade-reason-react#installation)
6. [Migration guide for ReasonReact Native](https://reasonml-community.github.io/reason-react-native/en/docs/migration/jsx3/) (useful for web developers as well)
6. [Alternative migration script](https://github.com/bloodyowl/upgrade-reason-react-esy) (worth to check, it may work with your project!)