ReasonML, React and Routing

In part 1 and part 2 of this series, I’ve put together the building blocks for making calls to an API and then processing the JSON returned into a Reason data structure.

My next steps in translating from a Javascript based React application to one based on ReasonML and Reason-React is to add routing.

In ReactJS we would typically use react-router for our routing.  However a common approach when using Reason is to use Director and to learn how to do that I recommend this article on integrating Director into a Reason app .

Rather than implement a different top level component for each route as we see in the article, what I want to do is have a single top level component that renders the appropriate children based on the route.   My aim is to have an app that starts with a login page.  After successful login the user will see a list of clients.  Clicking on a client will show a list of the servers belonging to that client.

The routes I want will look like this:

  • /login    which takes me to the login page
  • / takes me to the clients list
  • /clients/:client_id/servers  takes me to the servers list for a particular client

It’s pretty simple to declare the router for handling the routes above:

let router =
  DirectorRe.makeRouter {
    "/login": "login",
    "/": "clients",
    "/clients/:client_id/servers": "servers"
  };

A variant type allows us to pass the current route to our top level component (and also perhaps store it in our state). The key here is that the parameters passed in through the routes (such as the client_id for servers) are part of the route type (in this case the int for ServersRoute). For convenience I create this in a module called Types.

type routes =
  | LoginRoute
  | ClientsRoute
  | ServersRoute int;

This helper function renders the top level component.

let renderForRoute (route: Types.routes) => {
  let element = <App route router /> ;
  ReactDOMRe.renderToElementWithId element "root";
  ()
}

We need to tell the router how to map from a route in the URL to our route type and what to render. Again the most interesting handler here is where we handle the servers route, as this is where we extract the client_id from the URL and store it in our route representation.

let handlers = {
  "login": fun () => renderForRoute LoginRoute,
  "clients": fun () => renderForRoute ClientsRoute,
  "servers": fun (client_id: string) =>
    renderForRoute (ServersRoute (int_of_string client_id))
};

And finally, we configure the router and give it a default route:

DirectorRe.configure router {"html5history": true, "resource": handlers};

DirectorRe.init router "/login";

App is our top level component. It can be any sort of component, and its main job is to render the appropriate children based on the route. Here’s a simple stateless component declared in App.re that does just that:

let component = ReasonReact.statelessComponent "App";

let make route::(route: Types.routes) ::router _children => {
  ...component,
  render: fun self => {
   
    let loggedIn token => DirectorRe.setRoute router "/";
    let showServers client_id =>
      DirectorRe.setRoute router ("/clients" ^ client_id ^ "/servers");
    let element =
      switch route {
      | LoginRoute => <Login loggedIn />
      | ClientsRoute => <Clients clients=[] showServers />
      | ServersRoute client_id => <Servers servers=[] client_id />
      };
    <div className="App">
      <h2> (ReasonReact.stringToElement "Simple App") </h2>
      element
    </div>
  }
};

The loggedIn and showServers functions are used as callbacks by the lower level components, and are where the route is changed.  For my particular app loggedIn receives the authentication token from logging in, and showServers receives the client_id of the client clicked in the clients list renders by the Clients component.

This is my naive approach to routing using Reason and Director, and please do let me know in the comments if there’s a better way than this.  In my real application, the App component is actually a reducer component which lets me store state in the same was as Redux would in ReactJS.    I’ll cover combining this approach to routing along with stateful reducer components in my next article.

Leave a Reply

Your email address will not be published. Required fields are marked *