Better reducer workflow with ReasonML’s reducer components

As I started to build more complex applications, I used reducerComponents to maintain state.  There’s a great set of documentation on reducerComponents here but what I want to look at is how I improved by reducer workflow based on the advice of the tireless and ever helpful chenglou.

I had a child component that needed to update the state in its parent component, so I passed in a function to do that like this:

<Servers
  servers=self.state.servers
  showBackupJobs/>

The showBackupJobs function is given a server ID, sets that as the selected server in the state, and then makes an API call to fetch the backup jobs for that server.

My original function looked like this:

let showBackupJobs = server_id => {
  let str_server_id = string_of_int(server_id);
  self.reduce (_ => SelectServer(server_id));
  Rest.getBackupJobs(self.state.auth_token(str_server_id))
  |> Js.Promise.then_ (
    backup_jobs => {
      self.reduce ((_) => UpdateBackupJobs(backup_jobs));
      Js.Promise.resolve ()
    }
  );
  DirectorRe.setRoute(
  router,("/servers/" ++ str_server_id ++ "/backup_jobs"));
  ()
};

and the corresponding reducer was pretty simple:

| SelectServer(server_id) =>
  ReasonReact.Update({...state, selected_server: Some(server_id)})

Well, that’s a mess of code! State changes and related actions are separated in the code. However it can very easily be tidied up by pushing all the work into the reducer, and grouping the state update and the side effects together using ReasonReact.UpdateWithSideEffects.

The first step is to get rid of the intermediate function, and instead call the reducer directly with the action:

<Servers
  servers=self.state.servers
  showBackupJobs=(self.reduce (server_id => SelectServer(server_id)))
  />

The state change of my reducer remains the same, but the side effect code that was in the intermediate function now moves into the reducer as a side effect:

| SelectServer(server_id) =>
      ReasonReact.UpdateWithSideEffects(
        {...state, selected_server: Some(server_id)}
        (
          (self) => {
            let str_server_id = string_of_int(server_id);
            DirectorRe.setRoute(
              router,("/servers/" ++ str_server_id ++ "/backup_jobs"));
            Rest.getBackupJobs(self.state.auth_token,str_server_id)
            |> Js.Promise.then_ (
                 backup_jobs => {
                   self.reduce(_ => UpdateBackupJobs(backup_jobs));
                   Js.Promise.resolve()
                 }
               );
            ()
          })
        )

This really was a lightbulb moment for me in how to structure my ReasonReact apps better and coding and debugging my reducer functions just got a whole lot simpler!

Leave a Reply

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