Better architecture for integrating analytics into React/Redux using RxJS

Amir Alami
6 min readJan 17, 2021
Photo by Luke Chesser on Unsplash

It’s very common to think about the analytics as second class citizen in our codebase. They really don’t matter most of the time until, they do… As long as the project is simple and the number of events are small, it’s not a big deal. You fire the event right from the component and it is done.

Now, what happens if you have hundreds of different events spread across you your application and you don’t know where or when any of them are being fired? Or you just want to change your code to include another analytics platform as well? That was the situation I was facing in our codebase and in this article I’m gonna share my experience of how I applied a better architecture to handle the analytics inside our React/Redux applications.

You can also find the companion project here which I’ve applied the pattern in an example project.

In this article, I’m assuming that you’re already familiar with React and Redux

The Problem

Most of the time, we send an event to the analytics when some action takes place in our component and we call them in the event handler functions:

The problem with this approach is:

  1. Putting events in the component’s body, may cause redundant calls which ends up in wrong numbers in your analytics platform.
    This happened to me when we had a page view event in the body of the component, so whenever component was re-rendering, it would send an event to our analytics platform.
  2. When the number of events grow in your project it will be a nightmare to track and refactor them.
  3. The code can easily turn into a mess if an event that must be fired is based on some conditions which are not directly related to the current component.
  4. Components should not do anything other than presenting data to the users.

If we can strip away the analytics logic from our components, we will have a much cleaner structure and much more predictable events.

The Solution

Let’s breakdown the behaviors we might want to track via analytics in our applications.

We either track the user’s interaction with the UI which most of the time are tracked via automated tools like Hotjar and etc. or we track the user’s flow and actions in our application. This article will cover the second problem.

To track the user’s flow in a react application we must be able to at least observe the following changes in our app:

  1. Route changes
  2. State changes

One might ask, what if there’s some button in our application that doesn’t change the state and doesn’t affect the route… how this architecture would solve that? Well, my answer is: if a button doesn’t do any of those, it might not worth to track it after all. Or maybe, you don’t dispatch enough actions to your store and handle everything in the component level which is not a very good practice if you’re using state managers.

We can publish events from these two sources and make our analytics code to subscribe to those events. This way, we can move the analytics code into a separate location and loosen the coupling between our components and the analytics logic.

Separation of the analytics code

With this architecture, neither our store nor our components will have any idea about the existence of analytics, which is good.

Event Subject

Event subject is simply an observable that our analytics codes subscribe to it and whenever an event is received in the Event Subject our subscribers will be invoked. We will put our log event calls inside these subscribers.

You can create your own observable or use many tools that are available to create an observable. In this article, we will use RxJS.

RxJS is one the greatest tools to work with the event streams. It has a lot of operators which might come handy to fine tune to the events we want. If you’re not familiar with RxJS, make sure to checkout this video.

First, lets create a subject in a different location in our codebase, say: /src/analytics

Now we can publish events to this subject from everywhere in our application. To put it oversimplified, the subject will invoke all of the subscribers upon receiving an event, which in the above code is just a console.log() statement.

In the next step, we will create event sources that publish events to this subject.

1: State Changes: Redux Source

In order to track down the state changes, we should call our subject whenever an action is dispatched to our Redux store. One way to achieve this, is to write a simple custom middleware for Redux:

If you’re not familiar with how to write a custom middleware, you can check this article.

Now, every action dispatched to the store, will also send an event to our RxJS subject along with a few parameters:

  1. topic: this will be used later to identify store events from the router events.
  2. state: the store’s state before the action is applied.
  3. action: the action dispatched. Most of the time this is an object with two fields: type and payload.

Important Note: Don’t directly pass the store object to the subject. It comes with a dispatch method will can change the store’s state.

You can decide what is important to have in the analytics so feel free to adjust what you’re sending to the subject according to your needs.

2. Route Changes: React Router Source

If you’re managing your routes from your store, you can skip this part as it can be covered with Redux Source.

Route changes are also valuable for the analytics. To create an event source for the router, a wrapper component should created.

And the RoutesContainer component should look like this:

Any route change, will cause the listener to send an event to the subject with the following parameters:

  1. topic: This is used to identify the event source in the subscribers.
  2. route: This object contains the information about the current route. It has a field named pathname which shows the path of the current page.

Even though, we’re using react-router-dom as our routing library, the pattern can be applied to other router libraries.

Subscribers

Now that we have our event sources and the subject let’s create a subscriber which is invoked whenever an event is received.

Note that we’re using filter operator from RxJS to precisely tell the subject when it should invoke the subscriber. There are many operators that you can use and even mix and match to make sure that the subscriber is exactly invoked when appropriate. You can find more here.

Summary

Using this pattern, one can have a precise control over when to fire an event. You can use throttle, debounce, … operators from the RxJS to reduce the number of false events which might occur if you put your code in the render tree.

This pattern also does a better job from the Separation of Concerns perspective. Components, store and router don’t need to know about the existence of analytics at all.

You can also group all of your events into a single place which makes the maintenance much easier compared to the case when they’re buried deep in the components.

--

--

Amir Alami

Trying to share the experience of real-life software challenges, rather than hello worlds.