ReasonML by Example

ReasonML is new buzzword that has been popping around lately more and more often in the web development blogs and news. What is it? How to use it? Is it just a hype or not? This multi-part article mainly concentrates on “How to use it?” -part via simple CRUD example.

What is ReasonML?

ReasonML, or nowadays just Reason, is not a new language: it’s just a new syntax and a toolchain to OCaml. OCaml is 20-years-old functional programming language. Reason is created by Facebook and it’s suppose to attract more JS developers to start to use this statically typed functional language in web development. Reason also includes support for JSX and that’s why it’s also very good friends with ReactJS library. In ReasonML world the ReactJs is called ReasonReact. Browsers, of course, cannot execute Reason, so we use BuckleScript which compiles OCaml/Reason into readable JavaScript with smooth interop.

Why all these: Reason/ReactReason/OCaml/BuckleScript?

Without going to the details, here are few of the main points:

  • BuckleScript compiles Reason and OCaml very fast to JS (according to some people 10x faster than TypeScript)
  • BuckleScript creates very small bundles with great performance
  • Reason/OCaml has complete type system with type inference and 100% sound type system
  • Reason is pure, immutable and functional, BUT still allowing (in special cases) mutation and objects for familiarity and interop with existing JS-libraries

More information about  “What are Reason/OCaml/BuckleScript and  why should I use these?”:

How to use Reason and ReasonReact?

Best and up to date instructions you will find from the Official ReasonReact home page. But to get Reason and ReasonReact environment quickly up and running, you can use reason-scripts ( https://github.com/reasonml-community/reason-scripts ) provided by reason community. Seed/template app is very easy to set up with reason-scripts:

$ npm install -g bs-platform
$ yarn create react-app myapp --scripts-version reason-scripts
$ cd myapp
$ yarn start

Ok, now you have very simple template app but it doesn’t give you much pointers, how should you start to build your own real life application. Best to learn by example and head to Monad’s github and checkout the ReasonML CRUD example.

ReasonML CRUD example (part 1)

ReasonML CRUD example shows how to implement:

  • (Part 1) Simple statelessComponents and more complex stateful reducerComponents with subscriptions helper
  • (Part 2) Routing without using magic strings all over the application
  • (Part 3) Create, Read (~Show), Update and Delete entity using BlueprintJs components, bs-fetch and Js.Promise
  • (Part 4) Example bindings to BlueprintJS library
  • Module namespacing (Just see “ReasonML: basic modules” by Dr. Axel Rauschmayer, and you understand how it is done)

In this post we cover the first bullet point and in the future posts we cover the rest.

Stateless ReasonReact component

Here’s very simple stateless reason react component called ‘Home’ (in the example this module is View_home which comes from the fact that file is named view_home.re).

open Utils;
let component = ReasonReact.statelessComponent("Home");
let make = (_children) => {
  ...component,
  render: (_self) =>
    <div> (textEl("This is REASONML CRUD example.")) </div>
};

Here’s what happens in the code (line by line):

  1. Opening Utils module’s definition in this modules scope and so we can refer to its contents without prepending Utils module’s name
  2. Creating Home component template with ReasonReact.statelessComponent(“Home”). The string being passed is for debugging purposes (~ReactJS’ displayName)
  3. Defining make function which takes children as parameter
    • The make function is called by ReasonReact’s JSX processor
    • make requires you to return the component record. You’d override a few fields, such as the familiar render, initialState, didMount, etc.
    • Note the underscore in the parameter _children – this says to the compiler that it doesn’t give warning about the unused parameter (this can be used in every function)
  4. Spreading the component template default’s to the component record that we are returning from the make function
  5. Overriding the render function which has parameter _self (note the underscore again)
  6. Returning div element containing string element from the render function
    • All the components in ReasonReact JSX need to be typed reactElement that’s why we are using the opened Util module which has the textEl
    • textEl uses internally ReasonReact.stringToElement
    • Last line in the function is always the line which is returned from the function (here just one line <div> … <div>)

 

Stateful ReasonReact reducer component

Here’s the example’s stateful “root component” called App.

Utils.requireCSS("./app.css");

type state = {mainContent: ReasonReact.reactElement};
type action =
| MainContentChanged(ReasonReact.reactElement);

let component = ReasonReact.reducerComponent("App");
let make = (_children) => {
  ...component,
  initialState: () => {mainContent: Router.getInitialPage()},
  render: (self) =>
    <div className="app">
      <Blueprintjs.Navbar className="pt-dark">
        /* ... nav bar related elements ... */
      </Blueprintjs.Navbar>
      <div className="app-content"> self.state.mainContent </div>
    </div>,
  reducer: (action, _state) =>
    switch action {
     | MainContentChanged(el) => ReasonReact.Update({mainContent: el})
    },
  subscriptions: (self) => [
    Sub(() => Router.init((el) => self.send(MainContentChanged(el))), Router.destroy)
  ]
};

Here’s what happens in the code

    1. We require some css styles using our Utils module
      • Implementation in Utils is actually just:
        [@bs.val] external requireCSS : string => unit = "require";
      • Here we are actually using Reason’s (~BuckleScript’s) JavaScript interop utils. This is type safe binding to require function (and to be exact require function provided by css-loader npm package)!
      • Line tells compiler that we want do bind to external value function called  “require” which takes string as parameter and returns unit (~void/”no return value”), and we want our function to be called requireCSS in our module
      • More information about type safe bindings and different BuckleScript binding types from http://blog.klipse.tech/reason/2017/10/17/externals-js-ffi-reason.html
    2. We define state record which has one field called mainContent and it’s type is ReasonReact.reactElement
      • state is used by ReasonReact to get state structure of the
    3. We define action variant which has only one “constructor” (also called “tag”) called MainContentChanged with parameter ReasonReact.reactElement
      • These action constructors define the different operations that can be done to the component
      • Component state changes only through actions!
      • Reducer receives these actions and changes the state according to the action and it’s constructor parameters
    4. Then create App component template with ReasonReact.reducerComponent (note different function than statelessComponent) and we spread it in the beginning of the make function
    5. Then we override the template components initialState field
      • Initial state needs to be type state we defined earlier
      • We get the mainContent (ReasonReact.reactElement) from the Router module
    6. Render contains JSX but notice that we can add “self.state.mainContent” straight inside the div elements because it’s type is ReasonReact.reactElement
    7. Next we have the heart of the stateful component: reducer which
      • Gets the action and current state as parameters and returns new state
      • Action parameter is given to switch and compiler notices that it’s action variant type defined earlier
      • In switch we need to handle every different variant case separately or we get compilation error
      • Every case must return the state (because the switch returns the value that it’s cases’)
    8. Last we have the special subscriptions helper, which is used to register different services in the beginning and unregister them in the end of component life cycle
      • Here in the register we call Router.init with callback parameter and in the unregister we call Router.destroy
      • Router invokes the callback when URL/route has changed (more about this later)
      • Callback implementation is using “self.send” which is used to send actions to reducer
      • This means that when route changes, callback is called which causes the reducer to be called with “MainContentChanged” that has el (ReasonReact.reactElement)  as parameter. When the state changes it causes the ReasonReact to render the component again and then you have new mainContent reactElement in the view

End of part 1

This is end of the part 1 of ReasonML by example. In the next post we concentrate on how to create “type-safe routing without magic strings” in Reason.

 


Aki Haapamäki – Senior Software Engineer
Monad Ltd.