reaction-next-starterkit
uses NYTimes React Tracking to track analytics events throughout the app. By default, all events are sent to Segment. To enable tracking you can either configure Segment tracking to send to your account, or you can swap in a different analytics provider that you prefer.
You can see the source under /lib/tracking
By default, this Reaction Starter Kit uses Segment analytics tracking.
In the .env
file, at the root of the project, add or update the SEGMENT_ANALYTICS_WRITE_KEY
variable with your API key
SEGMENT_ANALYTICS_WRITE_KEY=ENTER_YOUR_SEGMENT_API_KEY
With the app running, navigate to different pages to trigger tracking events. Visit the Segment dashboard and verify that events are coming through successfully.
In the config/analytics
directory you'll see a file provider.example.js
. Copy and rename that file to provider.js
, where provider can be any name you'd like.
In the index.js
file in that same directory, import provider.js
and add it to the array.
import * as segment from "./segment";
import * as provider from "./provider";
export default [
Segment,
provider
];
The provider.js
file contains two functions you need to customize: dispatch
and renderScript
.
The dispatch
function gets called with all accumulated analytics data for an event. You must call the provider's API to track the event here.
/**
* Dispatch method
* @name dispatchSegmentAnalytics
* @ignore
* @param {Object} data Arguments supplied by tracking library
* @returns {undefined} No Return
*/
export function dispatch(data) {
// Example that works with google tag manager
window && window.dataLayer.push(data);
}
The renderStript
function renders a string of javascript code for your tracking service. For example in Segment.js
we render the Segment snippet and return a string. This string will be included in the document head.
/**
* Render string script
* @returns {String} String JS script to be applied to head
*/
export function renderScript() {
const { publicRuntimeConfig } = getConfig();
// Key API key
// add your api key to `publicRuntimeConfig` section of the next.config.js
const { apiKey } = publicRuntimeConfig;
// Return a javascript string that will be included in the HEAD of the rendered HTML document
return "STRING_SCRIPT";
}
You should now be able to send tracking data to the provider of your choice.
import React, { Component } from "react";
import track from "react-tracking";
@track(() => ({
action: "Page Viewed"
}), {
dispatchOnMount: true
})
class Page extends Component {
render() {
return <div>{"Page"}</div>;
}
}
import React, { Component } from "react";
import track from "react-tracking";
import TrackingPropType from "lib/tracking/TrackingPropType";
@track({ page: "SomePage" })
class Page extends Component {
static propTypes = {
tracking: TrackingPropType
}
@track({ action: "On Click with Decorator" })
handleClick = () => {
// Perform some action
}
handleOtherClick = () => {
this.props.tracking.trackEvent({ action: "On Click with trackEvent()" });
// Perform some action
}
render() {
return (
<button onClick={this.handleClick}>{"Track Event 1"}</button>
<button onClick={this.handleOtherClick}>{"Track Event 2"}</button>
);
}
}
import React, { Component } from "react";
import track from "react-tracking";
import TrackingPropType from "lib/tracking/TrackingPropType";
@track(() => ({
action: "Page Viewed"
}), {
dispatchOnMount: true
})
class Page extends Component {
static propTypes = {
tracking: TrackingPropType
}
@track({ action: "On Click with Decorator" })
handleClick = () => {
// Perform some action
}
handleOtherClick = () => {
this.props.tracking.trackEvent({ action: "On Click with trackEvent()" });
// Perform some action
}
render() {
return (
<div>
<button onClick={this.handleClick}>Track Event 1</button>
<button onClick={this.handleOtherClick}>Track Event 2</button>
</div>
);
}
}
Tracking the Product List Viewed
Segment event using the provided HOC trackProductListViewed
.
import React, { Component } from "react";
import PropTypes from "prop-types";
import withCatalogItems from "containers/catalog/withCatalogItems";
import track from "lib/tracking/track";
import trackProductListViewed from "lib/tracking/trackProductListViewed";
@withCatalogItems // Get catalog items for the current page
@track()
class ProductListPage extends Component {
static propTypes = {
catalogItems: PropTypes.array
}
// Track event on component mount
@trackProductListViewed()
componentDidMount() {}
render() {
const { catalogItems } = this.props.catalogItems;
return (
<div>
{catalogItems && catalogItems.map((catalogItem) => {
<div>{catalogItem.node.title}</div>
})}
</div>
);
}
}
Tracking for the Product Viewed
and Product Added
Segment events is provided by the trackProduct
HOC.
See src/components/ProductDetail/ProductDetail.js
for the full example.
import React, { Component } from "react";
import PropTypes from "prop-types";
import withCatalogItemProduct from "containers/catalog/withCatalogItemProduct";
import track from "lib/tracking/track";
import trackProduct from "lib/tracking/trackProduct";
import TRACKING from "lib/tracking/constants";
@withCatalogItemProduct // Product for page with route of `/product/:slugOrId/:variantId?`
@track()
class ProductDetailPage extends Component {
static propTypes = {
product: PropTypes.object
}
componentDidMount() {
const { product } = this.props;
// Select first variant by default
this.selectVariant(product.variants[0]);
}
selectVariant(variant, optionId) {
// Do something with selected variant / option
this.trackAction({ variant, optionId, action: TRACKING.PRODUCT_VIEWED });
}
// Expects the prop `product`, and an object with the following keys:
// `variant`, `optionId`, and `action` as a function arg.
// The `product` prop is provided by the `ProductDetailPage` component.
@trackProduct()
trackAction() {}
render() {
return (
<div>
{this.props.product.title}
</div>
);
}
}
Tracking the Product Clicked
Segment event provided HOC trackProductClicked
.
See src/components/ProductGrid/ProductGrid.js
for the full example.
import React, { Component } from "react";
import PropTypes from "prop-types";
import track from "lib/tracking/track";
import trackProductClicked from "lib/tracking/trackProductClicked";
@track()
class ProductGrid extends Component {
static propTypes = {
products: PropTypes.array
}
@trackProductClicked() // Expects second arg of function to be product
onItemClick(event, product) {
// Do nothing, Link component handles routing
}
render() {
const { products } = this.props;
return (
<CatalogGrid products={products} onItemClick={this.onItemClick} />
);
}
}
Data for the Segment e-commerce event Product List Viewed, will require values from a CatalogItemProduct
and CatalogItemVariant
.
{
// ID of the list being viewed. Should be a tag id or home if it's the homepage
list_id: "",
// Name of tag or Home
category: "",
// An array of products
products: [
{
// Product id
product_id: product._id,
// SKU
sku: product.sku,
// First tag
category: product.tags.edges[0].nodes.name,
// Title of top level product
name: product.title,
// Vendor field from the top-level product
brand: product.vendor,
// Not used. Products in the grid do not have an associated variant
variant: undefined,
// Price using the minimum price
// Where index 0, should be the index of the pricing object related to the currency for this product or shop
price: product.pricing[0].minPrice,
// Quantity not available via GraphQL API
// Set to 1 as a default
quantity: 1
// Coupons not available via GraphQL API
coupon: null,
// Products only have currency based on shop
currency: product.shop.currency,
// Not used. Products in the catalog currently don't have a concrete position
position: undefined,
// Value based off of the proeduct min price multiplied by quantity (variant.price * quantity)
// In this case, use `product.pricing.price` as there is only 1 for the quantity
// Where index 0, should be the index of the pricing object related to the currency for this product or shop
value: product.pricing[0].minPrice,
// Use router to generate the product url
url: "",
// Primary image from product
image_url: product.primaryImage.URLs.original
}
]
}
Data for the Segment e-commerce event Product Viewed, will require values from a CatalogItemProduct
and CatalogItemVariant
.
{
// ID of top-level product
product_id: product._id,
// SKU
sku: product.sku,
// First tag
category: product.tags.edges[0].nodes.name,
// Title of top level product
name: product.title,
// Vendor field from the top-level product
brand: product.vendor,
// Varaint Id
variant: variant.variantId,
// Price from variant
price: variant.price,
// Quantity not available via GraphQL API
// Set to 1 as a default
quantity: 1
// Coupons not available via GraphQL API
coupon: null,
// Products only have currency based on shop
currency: shop.currency,
// Position based off of variant index
position: variant.index,
// Value based off of variant price multiplied by quantity (variant.price * quantity)
// In this case, use `variant.price` as there is only 1 for the quantity
value: variant.price,
// Use router to get current url
url: router.pathname,
// Primary image from product
image_url: product.primaryImage.URLs.original
}
Data for the Segment e-commerce event Product Clicked, will require values from a CatalogItemProduct
.
{
// Product id
product_id: product._id,
// SKU
sku: product.sku,
// First tag
category: product.tags.edges[0].nodes.name,
// Title of top level product
name: product.title,
// Vendor field from the top-level product
brand: product.vendor,
// Not used at the moment. Products clicked from the grid do not have an associated variant.
variant: undefined,
// Price using the minimum price
// Where index 0, should be the index of the pricing object related to the currency for this product or shop
price: product.pricing[0].minPrice,
// Quantity not available via GraphQL API
// Set to 1 as a default
quantity: 1
// Coupons not available via GraphQL API
coupon: null,
// Products only have currency based on shop
currency: product.shop.currency,
// Not used. Products in the catalog currently don't have a concrete position
position: undefined,
// Value based off of the product min price multiplied by quantity (variant.price * quantity)
// In this case, use `product.pricing.price` as there is only 1 for the quantity
// Where index 0, should be the index of the pricing object related to the currency for this product or shop
value: product.pricing[0].minPrice,
// Use router to generate the product url
url: "",
// Primary image from product
image_url: product.primaryImage.URLs.original
}