16
Tracking in React Apps
- 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
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.
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
To implements tracking we need to at least having an application. I have create a base project at:
What is the app functionality ?
- a news curation app that use newsapi
- there is 2 tab
Home
andTop News
- Each tab have
refresh
news functionality - Each news card linked to respective article website
What are we going to track ?
- track every click on
go to source
button, we want to evaluate whether user usually go totops 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 clickrefresh 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
}
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.
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
toHeap
. we need to manually refactor all of ourMixPanel
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 ?
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.
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:
- initial data, means this data available for the rest of the child component.
- is the options which consist of
dispatch
,dispatchOnMount
,process
, andfowardRef
more detail check react-tracking
useTracking
will return object with 3 properties:
-
trackEvent
: a function to send data to be process atprocess
, thendispatch
. -
getTrackingData
: a function that return current initial data in our tracker. -
Track
: a HOC that wrapped a child component to give scope to it's initial data,process
anddispatch
logic. which later can be triggered usingtrackEvent
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 ) - Initial value only available to child component if we wrapped them with
<Track></Track>
. that why we wrapped<ArticleCard>
inHome.js
line 57 - 63, so it get the initial value fromHome.js
useTracking, otherwise it will only have initial value ofReactTrackingInitializer.js
.
Now back to the problem, let say we need to:
- change MixPanel to Heap
- add user data to every tracker, because we have new login feature
just see the difference between branch rtracking
and rtracking-solution
.
Changes need #1
peterchu999
posted on
Changes need to solve the problem statement:
- change MixPanel to Heap
- add user data, because we have add login feature
and compare it to the difference between branch direct
and direct-solution`.
Changes Need -> Direct Solution #2
peterchu999
posted on
Changes need to solve the problem statement:
change MixPanel to Heap add user data, because we have add login feature
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.
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 😊
nytimes / react-tracking
🎯 Declarative tracking for React apps.
16