image-credits: Rahul Raveendran

ReasonReact: Abstracting your way out with ReasonML Functors

Prateek Pandey
Porter Blog
Published in
6 min readOct 7, 2019

--

This article is a continuation of a previous blog-post which introduces ReasonML for Frontend web development. I encourage you to go through the blog-post for making the most out of this write-up.

Frontend design and development is a rapidly transforming landscape.
A fast growing tech coupled with demanding product expectations implies adhering to a strict development timelines giving little room for improvisations. As a developer you would have witnessed instances where we end up writing similar pieces of code multiple times.

This code duplication comes in various shapes and sizes such as handling data fetching operations, parsing API responses, writing business logic and use-cases, rendering of views and many more. Initially it falls in the never mind territory as long as our code is fulfilling the product goals and the end user is happy. But eventually it ends up in the chaotic zone where adding a small feature becomes problematic, fixing one bug creates another, the mutual blame games start, PRs become hefty and eventually we end up despising the look of our codebase.

We can do better if our codebase is hand in hand with programming fundamentals. One such principle which brings the much needed relief into our development cycles is Abstraction.

At the core, React is all about writing good components aka abstractions.
In the following post we explore how we developed a reason-react component, that abstracts data fetching and rendering operations in our reason-react applications.

For simplicity, we have an application which fetches a list of posts and displays them as list.

To see how we can use abstraction to our advantage, we will refactor our existing code from our previous blog post, using our custom abstraction component -The Loader Component.

Have a look at the implementation of the Loader Component as shown below. The implementation is done using the latest Version 0.7.0 of ReasonReact, which adds support for the React Hooks API. If you are not familiar with the same, I suggest you have a look here.

Loader-Component

If we look at the loader component carefully, the flow is similar to our Posts Component (from the previous blog) with the difference, it takes how to fetch the data and what to render as inputs instead of having the information encapsulated within the component.

Let us go over the exciting stuff happening inside the LoaderComponent

  1. How does the Loader Component know the type of data it would be fetching?
    We parameterize our LoaderComponent with module Config (of type LoaderConfig) as shown below, to let it know the type of data it would be receiving as a response from the API call.
module type LoaderConfig = {
type dataType
};

2. How do I define my state when I am not sure about type of data the component would be operating on?

As shown in the above code snippet, the dataType field (Config.dataType) of the Config module (being passed as input to the loader component) is used by the loader component to define its props( fetchData and renderView), state and the actions

~fetchData: unit => Js.Promise.t(Config.dataType)type state = Loadable.loadable(Config.dataType)

3. How can we use the Loader Component in our code-base?

To demonstrate it, let’s integrate our Loader component with the initially defined Posts Component and you will see the magic :-). We create a new component PostsComponentWithLoader which uses the LoaderComponent abstraction to handle its data fetching and rendering operations.

Using loader-component to fetch and render posts

So what exactly has happened here?

The Loader Component is a Functor.

As beautifully stated by Axel Rauschmayer in his book Exploring ReasonML and Functional programming, there are two ways of looking at a functor in ReasonML

A function whose parameters are modules and whose result is a module.

A module(the result) that is parameterized(configured) via modules (the parameters).

More on ReasonML modules and functors here.

Lets dissect the Loader Component integration step by step :-

a.) create the parameterized loader-component module

module  LoaderComponentConfig = {
type dataType = Posts.posts
};
module PostsLoaderComponent = LoaderComponent.LoaderComponent(LoaderComponentConfig);

Here we create a PostsLoaderComponent instance from the LoaderComponent module by passing it a module named LoaderComponentConfig .The LoaderComponentConfig has the type definition for the data type (Posts.posts) . As already stated this data type is used by loader component to understand the type of data it would be receiving. If you observe carefully the state and actions defined inside the loader-component too are parameterized by this data-type.

b.) instantiate the parameterized loader-component module

render: (_self) => {
<PostsLoaderComponent
fetchData=(() => Network.fetchPosts())
renderView=((posts: Posts.posts) => <PostsViewComponent posts />)
/>
}

Once we have the PostsLoaderComponent module, we instantiate it inside the render method of PostsComponentWithLoader. We pass PostsLoaderComponent the two essential props fetchData ( how to fetch the posts) and renderView ( what to render once the posts are fetched). The PostsComponentWithLoader is just a presenter component having delegated all the responsibilities of rendering and data fetching to the PostsLoaderComponent, which is nothing but an instance of the Loader-component.

And we are done :).

How about refresh operation?

So far our loader-component does well to fetch the data once and render the view. But what about the use-cases where we need to refresh the view, by refetching the data.

For e.g: A simple use-case would be to refresh a list of invoices, every-time we create a new invoice from the same view.

We need to find a way to tell the loader-component to re-invoke the API, which basically re-triggers the whole rendering cycle. We achieve this by passing a function, alongside the result which we already pass in the renderView method. The parent component can use this function to invoke the API call again.

Lets implement the changes:-

1.) We add a state dependency in useEffect, so that when the state changes useEffect hook is invoked again, which in turn invokes the fetch API call.
Notice the useEffect0 (from the initial code-base) goes to useEffect1, indicating that it has one dependency now.

2.) The API call inside useEffect should be conditional (depending on the value of the state) otherwise if we make the API call on every state change we would end up in an infinite loop of state change-render cycle.

Here we make the fetch API call only when the value of state is Loading.

Both the modifications discussed above can be seen in the code snippet below.

React.useEffect1(() => {
switch(state) {
| Loadable.Loading => Js.Promise.(
fetchData()
|> then_((result) => resolve(setState(_ => Loadable.Success(result))))
|> catch(_error => {
resolve(setState(_ => Loadable.Error))
})
|> ignore
)
| _ => ()
}
None;
}, [| state |]);

3.) We create a refresh function which changes the state to Loading, triggering the fetch API call

4.) Finally we pass the refresh function to the outside world through renderView callback as shown below.

<div>
{
switch (state) {
| Init => <div>(ReasonReact.string("Init..."))</div>
| Loading => <div>(ReasonReact.string("Loading"))</div>
| Success(result) => renderView(result, _ => setState(_ => Loadable.Loading))
| Error => <div>(ReasonReact.string("Error..."))</div>
};
}
</div>

Please find the modified loader-component code snippet below

Enthralled ! I bet, because I would be. The code is leaner in size, cleaner to write and easier to debug. From a scalability point of view, this is a win win situation as we can write so many components like PostComponent by reusing the LoaderComponent.

This is the power of a thoughtful refactoring. All we need is that little room for re-thinking and improvisation, before we make our final commits :-)

Special thanks to Tejesh Konchada , Ambuj Singh, Shubham Gupta who were instrumental in the design and implementation of the loader component :)

A big thanks to the team@Porter for their help and support.

Please feel free to put your feedbacks/questions in the comment section.
I am on twitter and insta anytime you need to talk more about ReasonML , Frontend-Development, building products that matter, cooking healthy recipes, fitness, traveling, and anything else that makes sense.

References:

  1. Exploring ReasonML and functional programming by Axel Rauschemeyer
  2. Advanced Reason-React: Writing Higher Order Components by Jared Forsyth

--

--