Reasonable React — Part 1

Building a React Frontend Powered by ReasonML

Brandon Konkle
Ecliptic

--

As my ReasonML-powered GraphQL API nears full release, I’ve been diving into the front end to unleash the power of expressive type safety and functional programming on my React and Apollo workflow. I’m still in the progress of building out usable patterns, but I’ve made some great progress and I’m ready to start sharing!

If you need a refresher on ReasonML and why you should consider using it, check out the beginning of my backend post. Todays article covers React components and styling. Future articles will dive into global state management and side effects, and GraphQL communication with apollo-client. Let’s get started!

React Components

The tagline for the official ReasonReact interface is “All your ReactJS knowledge, codified.” It sounds quite promising, but what does it actually mean? As a React developer, you have a lot of internal domain knowledge that you use to decide how to combine your components together, manage your data, handle side effects, and thread props through a shifting presentational structure.

The type system that Reason uses, from the OCaml programming language that backs it, is rich enough to express a great deal of that domain knowledge. It can help you manage a lot of the chaos you keep track of in your head, pointing out all of the impacts your changes have.

Need to change a prop from optional to non-optional? The type system can lead you to all of the places that are expecting an optional prop so that you can update them to account for the change. Need to add a new action? The type system can lead you to each place you need to account for the new action in your logic.

There’s a tradeoff, however (isn’t there always?). It does take some extra effort and a little verbosity to encode the types in a useful way. This means that you’re taking some time up front to pay off what would have become technical debt in a plain JavaScript project. All of the edge cases and failure states that you skip past in the loose land of JS need to be thought about and handled in ReasonReact. You need to account for that extra time, but I consider it an investment in the long-term stability and maintainability of the project.

A Simple Start

Now that you know a little of the pros and cons, let’s dive in by creating our first React component! ReasonReact takes advantage of OCaml’s rich labeled and optional arguments to make it easier to manage props in a type safe way. To interop with the existing React ecosystem, you can wrap your Reason components with a function that takes plain JS props and converts them to Reason props. Conversely, to use plain JS components you wrap them with a function that takes Reason props and maps them to plain JS props.

The first thing the example above does is open ReasonReact;. This is a convenience because of how frequently we use functions from that module. It makes those functions available in the current scope.

Next, I do a “selective local open”, assigning Js.Option.getWithDefault to a local variable. I do this out of habit so that I don’t pollute my module scope with values I’m not using.

Next, I choose a component “spec” by assigning statelessComponent("Header") to component. The statelessComponent function takes a component name (which shows up in debugging tools) and returns a Reason record with fields like didMount and willUpdate. This record is the Reason equivalent of a React Class, and it contains default implementations for things like lifecycle functions.

For my purpose, I’m just using a stateless component with no retained props and no state. I override the render field to render my component. The render function for a Reason component takes self as an argument, which gives it access to things like the local state. I’m not using it, however, so I use the OCaml convention of prefixing it with an underscore, naming it _self. This tells the compiler that we’re not using that argument.

Before returning the JSX I need for my header, I choose a message to display. I use the reverse application operator (|>) to pass the message prop into the getWithDefault function from Js.Option. Since we used ~message=? with a question mark at the end to specify the prop, we’ve made it an optional prop that Reason will wrap with an Option. We supply our default message if the prop was omitted.

The stringToElement function is mostly for type safety — it wraps your string in a type indicating it is intended to be used as a React element.

User Interaction

What if you have a user that actually needs to do something with your component? We need to hook into the React event model, but we can take advantage of Reason’s built-in reducers to keep things well controlled. I’ll start out by defining a few types:

I set up a type to represent a paperClip object, which is a plain JS object. The . indicates that it is a “closed” object — no other attributes than what are defined here. The " quotes around the key indicate that this is a JavaScript object, not a Reason record.

The action type is a Variant in OCaml lingo, but it should be familiar to those of you who have used “union” or “sum” types in other type systems. An action can be either LoadPaperClips or SetPaperClips. The set action takes an argument — the array of paperClips that we wish to set in the component’s state.

Next, I’ll define how to fetch my paperClips:

The PaperClipClient.fetchAll function takes a unit (meaning no arguments), calls the server, and returns a promise that resolves with a plain JS object. The object has a data property that contains an array of paperClip objects. I rename it to fetchPaperClips locally for semantic convenience. I set up my handler function to take the self record and fire the SetPaperClips actions with results when it is done.

Now, it’s time to define the component:

There’s a lot going on here! First, we’re using reducerComponent as our spec rather than statelessComponent. This gives us access to self.state and self.send, and allows us to put the types we defined above to good use.

We take care of providing the initialState function, and then jump into the reducer. This function takes an action and a current state, and it expects you to return one of ReasonReact’s state update types. I want the LoadPaperClips action to trigger my fetch, and then fire a resulting action that populates the paperClips in the component’s local state. To do this, I use the UpdateWithSideEffects type.

This type takes two arguments — the new state resulting from the action, and a callback function that receives the self record. I update the state to show that paperClips are loading, and then fire my fetchPaperClips promise to retrieve items from the server. I use the handleFetchPaperClips function I defined above to accomplish this, which uses self.send to send the SetPaperClips action when it’s done.

To use my array of components as the children for a DOM element, I use the arrayToElement utility. I was previously using createDomElement for this purpose, but the arrayToElement utility handles this scenario in a much more readable fashion.

Styles and Design Systems

Alright! Now that we know how to display things and handle user input, how do we make it pretty? Enter the bs-css library based on glamor. It wraps glamor’s object-based styles-in-JS with a layer of type safety. It’s been easy to work with in my experience, and seems to afford the power you need to accomplish anything in css.

A Top-Level Theme

To get my styles started, I like to create a top-level Theme.re file defining common styles shared across the application. I begin with a utility and some colors:

The unit utility gives me a configurable unit to calculate against rather than using something like em. My colors are passed to the hex function provided by bs-css. This function gives expression a `hex(string) type. Why is the type prefixed with a backtick (`)? You don’t need to worry too much about this yet, but know that this means the type uses OCaml’s “polymorphic variants”. It allows us more flexibility in how we pass around the types that bs-css uses, but you don’t need to worry about how until much later on.

Next, I set up some fonts:

With that, I’m ready to fill in a couple of basic styles used across the app:

Here we dig into the meat of how bs-css works. To generate a className for use with React, we use the style function. This function takes a list of rules. Each rule is an expression from bs-css that results in a rule declaration. For example, the color function takes a type that matches this expression:

Our hex function call above wraps the string in the type that bs-css needs to figure out what to do with that declaration.

My copy style is actually a function that takes some props before returning the style. This allows you to use props to structure reusable styles just like you would a re-usable component.

Styling Components

To make use of this in our components, we simply pass the results of style to the className prop:

The pattern I’ve settled on is creating a local Styles module for each component that needs local styles. This module opens both Css from bs-css, and Theme — our theme module that we created above. Then, it defines a header style that makes use of Theme.Fonts.Sizes to define the font size.

Scratching the Surface

We’ve come a long way in a short amount of time! We can now do all of the basic things we need to in React, including user input and styles. As I mentioned, next I’ll be diving into global state management, and then I’ll tackle GraphQL interaction with Apollo.

I’ve really found a great groove with ReasonML. At this point, I’m totally sold on it being the future of sound, expressive, functional interfaces. I’m also making great progress with the ecosystem on the backend as well. It takes some effort to learn and get used to, but it quickly becomes a valuable ally as you face the chaos of the front end.

In the meantime, we’re looking for new and interesting contract work at Ecliptic! If you have an ambitious project you need help to realize, we’re an email away! Let’s talk!

--

--

Brandon Konkle
Ecliptic

Founder and Lead Developer at @eclipticdev, @reasonml acolyte, supporter of social justice, enthusiastic nerd, loving husband & father.