React Hooks in ReasonML + ReasonReact

In April 2019, ReasonReact 0.7.0 was released, and with it support for React Hooks in ReasonML. I'm finally getting around to upgrading some components, and find myself fully drinking the React Hooks Kool-Aid as it pairs so well with Reason.

ReasonReact 0.7.0 brings the ability to write function components in ReasonML. Previously complex reducerComponent logic is now much more elegant. ReasonReact supports all of the hooks provided by React. There are a few minor differences, detailed in the documentation, but jumping in and converting an existing component to a function component using Hooks is pretty straightfoward.

Let's convert a basic component. Prior t0 v0.7.0, say you had the following component:

type state = {value: string};

type action =
  | UpdateValue(string);

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

let make = _children => {
  ...component,
  
  initialState: () => {value: ""},

  reducer: (action, _state) =>
    switch (action) {
    | UpdateValue(updatedValue) => ReasonReact.Update({value: updatedValue})
    },

  render: ({state, send}) => {
    <input
      value={state.value}
      onChange={event => send(UpdateValue(ReactEvent.Form.target(event)##value))}
    />;
  },
};

Using the power of React Hooks, that component can now be simplified to:

[@react.component]
let make = () => {
  let (value, setValue) = React.useState(() => "");
  
  <input value onChange={event => setValue(_ => ReactEvent.Form.target(event)##value)} />;
};

Yes - we really did just go from 24 lines to 6 (I have refmt set to column width of 100, FWIW).

If you're already familiar with Hooks, this probably doesn't come as a shock to you. But in writing Hooks in Reason, there are a few things to note:

  • In React, useState takes either an initial value or a function that returns an initial value. In order to ensure safety, Reason only accepts the function form.
  • Some hooks, such as the Effect hook, can take an array as a second argument. The ReasonReact Effect hook can take an array as well, except useEffect0 all the way through useEffect7 is provided, so that Reason knows exactly how many arguments to expect in the array. Type safety is a first class citizen in Reason.

There are a few other gotchas, such as ensuring useEffect returns an option type, but the best policy I've found when writing Hooks in Reason (and when writing anything else in Reason, too!) is to:

  1. Listen to the compiler
  2. Read the source code

You can check out the rest of the React Hooks here. There are some really awesome ones, like useMemo which allows you to easily add memoization to your React app.

If you want to chat about Reason, Hooks, or anything else, hit me up on Twitter.