NYTimes/react-tracking

Name: react-tracking

Owner: The New York Times

Description: ? Declarative tracking for React apps.

Created: 2016-12-14 00:50:54.0

Updated: 2018-05-22 14:18:26.0

Pushed: 2018-04-03 20:05:17.0

Homepage:

Size: 284

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

react-tracking npm version

Read more in the Times Open blog post.

Installation
install --save react-tracking
Usage

@track() expects two arguments, trackingData and options.

Tracking props

The @track() decorator will expose a tracking prop on the component it wraps, that looks like:


 tracking prop provided by @track()
acking: PropTypes.shape({
// function to call to dispatch tracking events
trackEvent: PropTypes.func,

// function to call to grab contextual tracking data
getTrackingData: PropTypes.func,


This PropType is exported for use, if desired:

rt { TrackingPropType } from 'react-tracking';

Alternatively, if you just want to just silence proptype errors when using eslint react/prop-types, you can add this to your eslintrc:


ules": {
"react/prop-types" : ["error", { "ignore": ["tracking"] }]


Usage as a Decorator

react-tracking is best used as a @decorator() using the babel decorators plugin.

The decorator can be used on React Classes and on methods within those classes. If you use it on methods within these classes, make sure to decorate the class as well.

rt React from 'react';
rt track from 'react-tracking';

ck({ page: 'FooPage' })
rt default class FooPage extends React.Component {

rack({ action: 'click' })
ndleClick = () => {
// ... other stuff


nder() {
return (
  <button onClick={this.handleClick}>
    Click Me!
  </button>
);


Usage on Stateless Functional Components

You can also track events by importing track() and wrapping your stateless functional component, which will provide props.tracking.trackEvent() that you can call in your component like so:

rt track from 'react-tracking';

t FooPage = (props) => {
turn (
<div onClick={() => {
    props.tracking.trackEvent({ action: 'click' });

    // ... other stuff
  }}
/>



rt default track({
ge: 'FooPage'
ooComponent);

This is also how you would use this module without @decorators, although this is obviously awkward and the decorator syntax is recommended.

Custom options.dispatch() for tracking data

By default, data tracking objects are pushed to window.dataLayer[] (see src/dispatchTrackingEvent.js). This is a good default if you use Google Tag Manager. You can override this by passing in a dispatch function as a second parameter to the tracking decorator { dispatch: fn() } on some top-level component high up in your app (typically some root-level component that wraps your entire app).

For example, to push objects to window.myCustomDataLayer[] instead, you would decorate your top-level <App /> component like this:

rt React, { Component } from 'react';
rt track from 'react-tracking';

ck({}, { dispatch: (data) => window.myCustomDataLayer.push(data) })
rt default class App extends Component {
nder() {
return this.props.children;


NOTE: It is recommended to do this on some top-level component so that you only need to pass in the dispatch function once. Every child component from then on will use this dispatch function.

When to use options.dispatchOnMount

You can pass in a second parameter to @track, options.dispatchOnMount. There are two valid types for this, as a boolean or as a function. The use of the two is explained in the next sections:

Using options.dispatchOnMount as a boolean

To dispatch tracking data when a component mounts, you can pass in { dispatchOnMount: true } as the second parameter to @track(). This is useful for dispatching tracking data on “Page” components, for example.

ck({ page: 'FooPage' }, { dispatchOnMount: true })
s FooPage extends Component { ... }

Will dispatch the following data (assuming no other tracking data in context from the rest of the app):


ge: 'FooPage'

Of course, you could have achieved this same behavior by just decorating the componentDidMount() lifecycle event yourself, but this convenience is here in case the component you're working with would otherwise be a stateless functional component or does not need to define this lifecycle method.

Note: this is only in affect when decorating a Class or stateless functional component. It is not necessary when decorating class methods since any invocations of those methods will immediately dispatch the tracking data, as expected.

Using options.dispatchOnMount as a function

If you pass in a function, the function will be called with all of the tracking data from the app's context when the component mounts. The return value of this function will be dispatched in componentDidMount(). The object returned from this function call will be merged with the context data and then dispatched.

A use case for this would be that you want to provide extra tracking data without adding it to the context.

ck({ page: 'FooPage' }, { dispatchOnMount: (contextData) => ({ event: 'pageDataReady' }) })
s FooPage extends Component { ... }

Will dispatch the following data (assuming no other tracking data in context from the rest of the app):


ent: 'pageDataReady',
ge: 'FooPage'

Top level options.process

When there's a need to implicitly dispatch an event with some data for every component, you can define an options.process function. This function should be declared once, at some top-level component. It will get called with each component's tracking data as the only argument. The returned object from this function will be merged with all the tracking context data and dispatched in componentDidMount(). If a falsy value is returned (false, null, undefined, …), nothing will be dispatched.

A common use case for this is to dispatch a pageview event for every component in the application that has a page property on its trackingData:

ck({}, { process: (ownTrackingData) => ownTrackingData.page ? {event: 'pageview'} : null)
s App extends Component {...}



ck({page: 'Page1'})
s Page1 extends Component {...}

ck({})
s Page2 extends Component {...}

When Page1 mounts, event with data {page: 'Page1', event: 'pageview'} will be dispatched. When Page2 mounts, nothing will be dispatched.

Advanced Usage

You can also pass a function as an argument instead of an object literal, which allows for some advanced usage scenarios such as when your tracking data is a function of some runtime values, like so:

rt React from 'react';
rt track from 'react-tracking';

n this case, the "page" tracking data
s a function of one of its props (isNew)
ck((props) => {
turn { page: props.isNew ? 'new' : 'existing' }

rt default class FooButton extends React.Component {

 In this case the tracking data depends on
 some unknown (until runtime) value
rack((props, state, [event]) => ({
action: 'click',
label: event.currentTarget.title || event.currentTarget.textContent
)
ndleClick = (event) => {
if (this.props.onClick) {
  this.props.onClick(event);
}


nder() {
return (
  <button onClick={this.handleClick}>
    {this.props.children}
  </button>
);



NOTE: That the above code utilizes some of the newer ES6 syntax. This is what it would look like in ES5:

..
rack(function(props, state, args) {
const event = args[0];
return {
  action: 'click',
  label: event.currentTarget.title || event.currentTarget.textContent
};

..
Accessing data stored in the component's props and state

Further runtime data, such as the component's props and state, are available as follows:

rack((props, state) => ({
action: state.following ? "unfollow clicked" : "follow clicked" 
name: props.name
)
ndleFollow = () => {
 this.setState({ following: !this.state.following })
}

Example props.tracking.getTrackingData() usage

Any data that is passed to the decorator can be accessed in the decorated component via its props. The component that is decorated will be returned with a prop called tracking. The tracking prop is an object that has a getTrackingData() method on it. This method returns all of the contextual tracking data up until this point in the component hierarchy.

rt React from 'react';
rt track from 'react-tracking';

ass a function to the decorator
ck(() => {
nst randomId = Math.floor(Math.random() * 100);

turn {
page_view_id: randomId


rt default class AdComponent extends React.Component {
nder() {
const { page_view_id } = this.props.tracking.getTrackingData();

return (
  <Ad pageViewId={page_view_id} />
);



Tracking Data

Note that there are no restrictions on the objects that are passed in to the decorator.

The format for the tracking data object is a contract between your app and the ultimate consumer of the tracking data.

This library simply merges the tracking data objects together (as it flows through your app's React component hierarchy) into a single object that's ultimately sent to the tracking library.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.