Beyond counters — Using recompose and RXJS 6 to build a (semi) complex UI

Taylor Thompson
9 min readJun 14, 2018
Photo by Vishal Banik on Unsplash

If you haven’t heard about reactive programming yet, I invite you to pause and get acquainted. This gist is a great place to get started. Reactive programming allows your applications to react to events and data streams in real time. This means you can skip life cycle methods like componentDidMount and componentDidUpdate when checking for changes in your application state. If you are already familiar with reactive programming, then hopefully this post can give you more insights on how it can dovetail seamlessly with React using the recompose library.

If you would like to skip to the end, the codesandbox is here

Update: If you are interested in how this may look with React hooks, I have written a blog post about it here: https://teukka.tech/datastructures.html

Task

While learning about reactive programming in the context of React, I found a lot of great material, but not many tutorials outside of simple data fetching and counting. Here we will build a set of three lists. The first list contains users, while the other two containers list their likes and dislikes.

Clicking on a user should then load their likes and dislikes list and change the URL to denote which user is currently selected. If no user is specified in the the URL, then the first user on the list should be chosen automatically. Likes and dislikes should also be able to be added, deleted, and edited.

Tools

The main tools we will be using are recompose and RXJS. Both are utility libraries that help you harness the power of reactive programming. I would suggest looking through the docs of both libraries to get acquainted with their usage, but I will also explain how to use them in the context of building a UI in React.

Why

UI design is supposed to feel snappy and reactive, and reactive programming allows us to change UI elements according to the flow of our data. Unlike in redux, we don’t have to update our data store, wait for the update to propagate, check to see if the update is finished, and finally update our UI to reflect the changes. With reactive programming, we can push changes to data streams and our components instantly react to these changes.

As our applications grow in size, so can the state management. The result is a large state object which needs to be traversed and updated even when you are only using a small part of it for the current page or view. In many cases, this data can be kept locally and can be ephemeral, no longer taking up space when the user navigates away from the page.

First steps

The first thing we need to do in this project is to set up recompose to work with the observable configuration used by RXJS. This is done by importing the function setObservableConfig from recompose, and passing it the from utility from rxjs.

Now that we have recompose configured to work with RXJS, we can start making prop streams for our component to consume. Let’s first start with a load stream which will fetch our users from the backend and pass the response — as well as any other props received — to the next stream.

Let’s break this down line by line.

First, we are importing a few operators from rxjs, so let’s discuss what they do and why we need them. switchMap and map allow us to map values to other functions and components. switchMap takes an observable and flattens it, allowing us to pass just its values to the next function and not the observable itself.

The following operators, tap, catchError, and startWith, help us with debugging and allows us to give the user information about the state of the application. tap takes and observable, performs a side effect, and returns a copy of the original observable.

Using tap to debug observables

Using tap to peer into the Observable stream

By using tap, we can log out the response we receive from the backend without having to worry about changing anything in our stream. Using the startWith and catchError operators help us communicate the status of the application to users by giving us the ability to do conditional rendering based on it’s current state. We will go into the implementation of this later.

Now that we understand what the operators we are using are able to do, let’s step through the load stream logic. You will notice that the variable load itself is the result of the mapPropsStream function taken from recompose this takes as an argument a function which receives a stream of props (streams are denoted by the suffix $ for clarity, but that does not have any syntactic meaning) which can then be piped through our logical operators.

Here is where switchMap becomes very useful. Since React can’t render anything with an Observable object, we need to flatten our props stream so that we get only the values of that stream. Those values are the actual props we need to render our component. So we flatten the stream and map it to our function which fetches the users from the backend. You can use props here to pass any additional arguments to your function that fetches the data, but since we’re faking it here, we don’t need to pass anything. Finally, our request returns a users array which we then map to an object which passes along the props, users, and signals a successful request by setting status to 'SUCCESS'.

You may have noticed that we use tap here as well to call the function setUserList, which is contained in the props passed to the load stream. This, combined with the ternary check of isEmpty(props.userList will help us prevent unnecessarily re-fetching the data. If we have the data, we just want to return it without any manipulation.

Using streams to handle DOM events

Let’s create a second props stream which handles choosing a user from the list and setting them as a selected user:

Much like in our load stream, we want to create a stream of props using recompose’s mapPropsStream function. However, instead of directly switchMapping the props, we want to create two variables — a stream and a handler — using another utility function from recompose, createEventHandler. createEventHandler will, as the name implies, create a handler and a corresponding stream which you can then assign to React events such as onChange, onClick, etc. The values from that event will then be passed into the stream which you can then pipe, map, and whatever else you need. In our case, we want to flatten both the incoming props stream and the stream from the event and produce a single object.

Taking our props, we want to check to see if there is a user specified in the URL. In this project, react-router-dom is being used so we find the user param in props.match.params.user. But where is the Route to pass the match prop to this stream?!? Don’t worry, we will get there, just know that for now, selectUser will be passed the props from react-router’s Route component. Now that we have our user from the URL, we want to start our stream with either that user, or the first user in our userList. If we don’t have the userList yet, we just want to start with null. Then we simply pipe the selectedUser through the stream and return an object which contains all props passed to selectUser as well as our userSelect function.

In order to use the two prop stream we just created, we need to make a component which will take the props from both streams and render our list element

The component itself is quite simple, it just takes the props, checks to see if the right user is in the URL, and if not, pushes the correct user there. Next, it takes our userList, maps through it and returns a list item for each user in the list and adds some conditional styling if the user is currently selected. If the request is still ongoing, then it displays a loading component. Notice that we pass the userSelect function to our list items to use as an onClick handler. The user that is passed to userSelect is then pushed to the selectUser stream.

In order for this component to have access to all these props, it’s time for some functional programming beauty. Using the compose function from recompose, we create a new element which streams all the props from both load, selectUser, and react-router to the IndexPage element. It is here we will also create the function setUserlist and the userList prop we saw in our load stream.

The functions withState and withHandlers allow us to keep persistent data and manipulate that data inside our streams. withState accepts three arguments, the name of the item in state, the name of the function used to update the state, and the inital state. withHandlers receive the function specified in withState and can accept as many handlers as you need. In our case, we just need one handler which will take the users array from our fetch function and set the userList state property to that array. We also pass the withRouter function so that our component and event streams have access to router props such as match and history.

Time to put the first steps together

Next Steps

Now that we have our list of users, it is time to create a component which will display their lists of likes and dislikes. Here, we can take advantage of more recompose helper functions, withContext and getContext. These will prevent us from being trapped in passed-down props hell. Since we will be creating more handlers to update the list of users, there will be many functions which we will want to pass down to our components that display the likes and dislikes which could result in something like this:

<InfoTables {...{addLike, addDislike, changeLike, changeDislike, deleteLike, deleteDislike, selectedUser}} />

All of these props would then have to be continually passed down until they reach the components that actually consume them. That is crazy! By using withContext and getContext, we can pass down the props we need and consume them in any child of our IndexPage component. So, let’s create our handlers:

Here, are a few basic functions that will update our state after making a request to a fictitious endpoint. We can now pass these handlers and our selected user down to child components using withContext

withContext takes two arguments, the first being the childContextTypes, and the second being a function which returns the props to be passed as context. In our case, we want to pass the selected user and our state handler functions down as context. We get these props by placing our withContext function as an argument of our compose function like this:

We are now able to consume these props as context in any child component!

Using context in child components

Let’s use the context we just created to display users’ likes and dislikes and add a simple interface to add and remove data.

The above component simply takes the user and their likes from context and displays them in a list. This component also allows users to delete a like by clicking on an icon which calls our deleteUser function we defined earlier. Let’s add a modal that will allow users to add an item to the list by clicking on the add icon.

It’s time to create more DOM event driven streams. The first stream we want to create will be one to open and close the form modal:

Handling a toggle event is quite simple, we specify whether we want the toggle to be on or off, and then use scan — a function that applies an accumulator over the stream ( similar to Array.reduce ) — to toggle the state. We then return all passed props as well as the current state of the modal and the handler itself.

Dealing with text input is a bit trickier:

In the above snippet, we take the stream produced by our event handler ( in this case typing in a text input ) and we create a new stream composed of only the value of event.target. It is also important to note that we must use startWith here or else this stream will not be subscribed to and our component which uses this stream will not be rendered.

To access these functions in our List component, we must compose them together like so:

By using composable streams, we are able to create a nice modal form which takes text input and adds it to the list of user’s likes. Here is it’s parent component:

And the modal itself:

Doing the same for the user’s dislikes gives us two lists that can be modified by the user.

Wrap up

Observable streams are a great way of managing data flows in components. While some of the things outlined here might be a bit overkill, I wanted to illustrate that composable streams work well in many use cases. Breaking up logic into streams can help create separate and composable pieces of logic that can stem from complicated UIs. Components that subscribe to changes in the streams are able to react to one source of truth rather than having to check for side-effects in lifecycle methods such as componentDidUpdate. I think that they are a great way to remove some of the anti-patterns we see in React components and to avoid the performance costs of a large Redux store.

--

--

Taylor Thompson

Web developer, functional programming fanboy, wannabe computer scientist