DEV Community

Sophia Brandt
Sophia Brandt

Posted on • Originally published at on

Learning ReasonReact Step by Step Part: 3


ReasonML + BuckleScript is now Rescript.

As the ecosystem has changed around those tools, this blog post is not accurate anymore.

In my last post I tried to create a custom hook function for React forms.

That didn't work as I expected. Some kind folks helped me out and gave me some suggestions.

Let's pivot and try something different. Instead of creating a custom hook, I'll take a step back and add the logic to the Form component. Maybe I can decouple it later.

Using a Js.Dict to store data (email and password) proved to be difficult and seems to be an anti-pattern.

The code we have so far is pretty bare-bones and can be seen on GitHub.

useReducer Hook With ReasonReact

As an alternative, I will write a useReduce hook and add the state as a ReasonML Record.

The good news is that records are typed. The bad news is that field names (keys) are fixed. So, I'll have to hard-code the data that I want to store.

/* src/ */
type state = {
  email: string,
  password: string,
Enter fullscreen mode Exit fullscreen mode

We set up our "storage container" type where email and password are strings.

useReducer almost works the same as in React.

Let's write the actions:

/* src/ */
type action =
  | SetEmail(string)
  | SetPassword(string)
  | SubmitForm;
Enter fullscreen mode Exit fullscreen mode

When someone types into the email field, we have to store the input. The SetEmail action/function takes a parameter with the type string.

The same is true for the password.

And after that, we have to handle how to submit the form values. The SubmitForm action doesn't take any arguments.

Now, for the useReducer:

/* src/ */

let reducer = (state, action) => {                   // (A)
  switch (action) {
    | SetEmail(email) => {...state, email}           // (B)
    | SetPassword(password) => {...state, password}
    | SubmitForm => {                                // (B)
      Js.log({j|Form submitted with values: $state|j});
      {email: "", password: ""};

let make = () => {
  let initialState = {email: "", password: ""};    // (D)

  let (state, dispatch) = React.useReducer(reducer,initialState); // (E)
Enter fullscreen mode Exit fullscreen mode

On line A, we create the reducer function with a switch statement on each action.

Our state is a Record, so we can use the spread syntax to update it (that looks like JavaScript!) (see line B).

SetEmail and SetPassword are almost identical.

SubmitForm (line C) uses a JavaScript console.log to log out our state. Then it resets the state to empty strings.

We have to use the strange looking syntax for string interpolation.

Inside the Form component I create an initial state with an empty email and password string (line D).

In React, we use a de-structured array to initialize the useReducer, i.e.:

const [state, dispatch] = React.useReducer(reducerFunction, initialState)
Enter fullscreen mode Exit fullscreen mode

Reason uses a tuple, but other than that, it looks similar to React (line E).

Now, we only have to hook up the dispatch function to our JSX:

/* src/ */
  let valueFromEvent = evt: string => evt->; // (A)

  <div className="section is-fullheight">
    <div className="container">
      <div className="column is-4 is-offset-4">
        <div className="box">
              evt => {
            <div className="field">
              <label className="label"> {"Email Address" |> str} </label>
              <div className="control">
                  onChange={evt => valueFromEvent(evt)->SetEmail |> dispatch} // (B)
            <div className="field">
              <label className="label"> {"Password" |> str} </label>
              <div className="control">
                    evt => valueFromEvent(evt)->SetPassword |> dispatch // (B)
              type_="submit" className="button is-block is-info is-fullwidth">
              {"Login" |> str}
Enter fullscreen mode Exit fullscreen mode

What's going on here?

I stole line A from Jared Forsythe's tutorial:

In JavaScript, we'd do to get the current text of the input, and this is the ReasonReact equivalent. ReasonReact's bindings don't yet have a well-typed way to get the value of an input element, so we use to get the "target element of the event" as a "catch-all javascript object", and get out the value with the "JavaScript accessor syntax" ##value.

This is sacrificing some type safety, and it would be best for ReasonReact to just provide a safe way to get the input text directly, but this is what we have for now. Notice that we've annotated the return value of valueFromEvent to be string. Without this, OCaml would make the return value 'a (because we used the catch-all JavaScript object) meaning it could unify with anything, similar to the any type in Flow.

We'll use this function to hook it up to our onChange function for the password and email fields (see line B).

First, we take the event and extract its value, then we pipe the function to our SetEmail or SetPassword action and lastly to our dispatch.

Why -> and |>?

The first one is Pipe First:

-> is a convenient operator that allows you to "flip" your code inside-out. a(b) becomes b->a. It's a piece of syntax that doesn't have any runtime cost.

The other one is Pipe Forward/Pipe Last/Reverse-Application Operator. It basically does the same. But some functions require you to add the thing that you pipe as the first argument, and some as the last.

It's a bit ugly. Most JavaScript and BuckleScript interop requires pipe-first. Ocaml and Reason native code works mostly with pipe-last.

Code Repository

The complete code is on GitHub.


useReducer works well with ReasonReact and will be very familiar to a React developer.

I like ReasonML's pattern matching and it's a good fit for useReducer.

Further Reading

Top comments (2)

theodesp profile image
Theofanis Despoudis

I'm beginning to lose you. I just don't understand why someone will use Reason when something simple such as merging two objects or getting the value of the event target gets "Un-Reasonable" complicated in the end?

sophiabrandt profile image
Sophia Brandt • Edited

Good question.
Updating the state in a reducer works the same in React though. If you mutate an object you hold in useState or useReducer with Javascript, you'll get a bug, too.
JavaScript allowing for dynamic values in objects is nice and easy for a developer but it's not type-safe and AFAIK also bad for the compiler as it cannot optimize before execution, as the object is like a "black box".
I'm not sure how Typescript handles that, but if you define an interface for the object, you are bound to those limitations, too.
But ReasonML has a lot of warts, one of them Javascript interoperability.
This blog post series is not my goal to sell you ReasonML or ReasonReact, but my exploration of creating an app.
I actually like the type safety, but I'm not sure if you couldn't have the same experience with Typescript.