Tracking in React Apps
Tags: javascript, react, monitoring, todayilearned
Published: 2021-12-25 11:30:38

## Disclaimer
- The code might not be a best practice, because it's based on personal experience.
- Example has been simplified, so we could focus on the tracking code and tools
- This post will not discuss or used any 3rd party implementation of specific tracking platform (crashlytics, data dog, sentry, mixpanel, etc)
- The 3rdParty Mocked code might be different from real 3rdParty API

## Notice
if you are interested in the application code more than the tracking implementation. Leave reaction to this post, I'll consider making another post to explain it.

## Tracking

Nowadays, tracking user experience is a must for most application, by collecting the tracked data from user we can develop, fixing or improve our feature (especially UX). 

Based on my experience tracking can be differ into 2 category :
- `product / marketing`: this tracking goals is to keep track and evaluate marketing approaches (FB ads, google ads, instagram link, etc), and help product team to evaluate UX
- `error`: this tracking purpose is to notify developer about the error that occur in production before customer making any complain.

Let's see the implementation in react code


## Application and tracking statement

To implements tracking we need to at least having an application. I have create a base project at:

{% github https://github.com/peterchu999/react-tracking-impl no-readme %}

**What is the app functionality ?**
- a news curation app that use [newsapi](https://newsapi.org/)
- there is 2 tab `Home` and `Top News`
- Each tab have `refresh` news functionality
- Each news card linked to respective article website

![News Apps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fn5gcnjxpv7sprkfm29t.png)

**What are we going to track ?**
- track every click on `go to source` button, we want to evaluate whether user usually go to `tops news` tab or not, so the Data expected looks like :
```
{
  eventName: 'click_go_to_source',
  page: 'Home / TopNews'
}
```
- track every click on `refresh feed` button, we want to evaluate whether user click `refresh feed` button or not so the data expected looks like :
```
{
  eventName: 'refresh_feed',
  page: 'Home / TopNews'
}
```
- track error when `fetching data`, we want to track every error occur when fetching data. Data expect to looks like :
```
{
  eventName: 'error_fetch',
  page: 'Home / TopNews',
  errorReason: stringify error object
}
```

## Usual Tracking (Direct Approach)

Basically it's just calling 3rd party sdk / api for event tracking or logging on every click handler or error catch

In this code example we will use Mocked `DataDog` for our **error** tracking and `MixPanel` for our **click** tracking. 

The code implementation can be seen in [link](https://github.com/peterchu999/react-tracking-impl/tree/direct). 

### Detail Code go through

**Click Go To Source Track**
every time the user click `go to source` this code will send over the data to mock `MixPanel`.
```
// ArticleCard.js
...
// line 7
const handleClick = () => {
  const eventName = "click_go_to_source";
  const unique_id = uuid();
  MixPanel.track(eventName, unique_id, {
    page,
  });
  ...
};
....
```

**Click Refresh Feed Track**
every time the user click `refresh feed` this code will send over the data to mock `MixPanel`.
```
// Home.js or TopNews.js
...
// line 26
const onRefreshClick = () => {
  const eventName = "refresh_feed";
  const unique_id = uuid();
  MixPanel.track(eventName, unique_id, {
    page,
  });
  ...
};
....
```

**Fetch News error Track**
every time our fetch to news from newsapi failed, this code will send  over the `fetch_error` data to mock `DDlog`.
```
// Home.js or TopNews.js
...
// line 15
onError: (err) => {
  const eventName = "error_fetch";
  DDlog.error(eventName, {
    page,
    errorReason: JSON.stringify(err, null, 4),
  });
},
....
```

It seems everything to work fine 🤔, yep that's what i thought, until some changes was needed because of new feature or 3rd Party tracking platform commercial issue / fees.

Imagine that we already put 100+ tracker over 10 screens, then we need to :
- change tracking platform, for example from `MixPanel` to `Heap`. we need to manually refactor all of our `MixPanel` tracking code 1-by-1 😵‍💫. 
- add additional tracking data since we have new login feature, now we want to track user data every too 🤯.

Gratefully, i encounter this problem when my tracker was still less than 20 😮‍💨. But there is a question pop up on my mind, do i need to change the code one-by-one every time there is commercial issue or new feature that affect current tracking ?

## React Tracking

That's what lead me to `react-tracking` by NYT, a React specific tracking library. it helps to :
- Centralize our tracking logic, yet compartmentalize tracking concerns to individual components
- Give tracking data a scope

Let's see the code implementation [link](https://github.com/peterchu999/react-tracking-impl/tree/rtracking). 

We create `ReactTrackingInitializer` HOC (High Order Component) to be our parent / root tracking wrapper.

```
const ReactTrackingInitializer = ({ children }) => {
  const { Track } = useTracking(
    {
      // this is where the initialize data put
      trackVersion: "1.0.0",
    },
    {
      dispatch: (trackedData) => {
        console.log("dispatchData", trackedData);  
    }
  );
  return <Track>{children}</Track>;
};
```

`useTracking` is a hooks version to implementing `react-tracking` which suitable for functional component, find out more on their docs if you still implementing class component.

`useTracking` takes 2 params:
1. initial data, means this data available for the rest of the child component.
2. is the options which consist of `dispatch`,`dispatchOnMount`,`process`, and `fowardRef` more detail check [react-tracking](https://github.com/nytimes/react-tracking)

`useTracking` will return object with 3 properties:
1. `trackEvent`: a function to send data to be process at `process`, then `dispatch`.
2. `getTrackingData`: a function that return current initial data in our tracker.
3. `Track`: a HOC that wrapped a child component to give scope to it's initial data, `process` and `dispatch` logic. which later can be triggered using `trackEvent`

From the reference we can implements our 3rd Party logic at `dispatch` option. so it will looks like this :

```
...
dispatch: (trackedData) => {
  console.log("dispatchData", trackedData);
  const { eventName, ...restOfData } = trackedData.data;
  switch (trackedData.type) {
     case "product":
       const unique_id = uuid();
       MixPanel.track(eventName, unique_id, restOfData);
       break;
     case "error":
       DDlog.error(eventName, restOfData);
       break;
     default:
       break;
  }
},
...
```

It looks a lot like `redux` reducers. Now you might ask there must be a dispatch mechanism to like redux, where is it ? checkout the code at `Home.js` line 25 - 33

```
const { trackEvent, Track } = useTracking({
  data: { page: "HOME" },
});

const onRefreshClick = () => {
  trackEvent({ type: "product", data: { eventName: "refresh_feed" } });
  refetch();
};
```

the `trackEvent` will send over the data below to our `dispatch` function.
```
{ 
  type: "product", 
  data: { 
    eventName: "refresh_feed",
    page: "HOME"
  } 
  trackVersion: "1.0.0"
}
```

Wait, Where did `trackVersion: "1.0.0"` and `page: "HOME"` came from 🙄 ? react tracking perform a merge operation on data we sent and initial data provided. in this case :
- data we send :
```
{ 
  type: "product", 
  data: { 
    eventName: "refresh_feed"
  } 
}
``` 
- initial value on `Home.js` useTracking :
```
{ 
  data: { 
    page: "HOME"
  } 
}
```
- initial value on `ReactTrackingInitializer` useTracking:
```
{
  trackVersion: "1.0.0"
}
```

We already utilize `react-tracking` 🎉🎉🎉, Just Note that:
- there must be at least 1 component that wrapping with `<Track></Track>` at root level (prefer to wrap <App/>)
- Initial value only available to child component if we wrapped them with `<Track></Track>`. that why we wrapped `<ArticleCard>` in `Home.js` line 57 - 63, so it get the initial value from `Home.js` useTracking, otherwise it will only have initial value of `ReactTrackingInitializer.js`.

Now back to the problem, let say we need to:
1. change MixPanel to Heap
2. add user data to every tracker, because we have new login feature

just see the difference between branch `rtracking` and `rtracking-solution`. 

{% github https://github.com/peterchu999/react-tracking-impl/pull/1 %}

and compare it to the difference between branch `direct` and direct-solution`. 

{% github https://github.com/peterchu999/react-tracking-impl/pull/2 %}

It will more work to be done when using 3rdParty Sdk / API directly, Imagine we have 10+ MixPanel tracker, it will cost a lot of time.


## Conclusion

React Tracking Help us to centralize the tracking logic so if there are any changes needed we can just refactor our dispatch function.

Thanks for reading, leave any comment below 😊

## Shout Out

{% github https://github.com/nytimes/react-tracking no-readme %}

{% medium https://open.nytimes.com/introducing-react-tracking-declarative-tracking-for-react-apps-2c76706bb79a %}