react-redux-analytics
Analytics middleware for React+Redux
To send metrics to tracking server (Google Analytics, Adobe Analytics), use corresponding plugins. (I will release them soon)
Requirements
- React (15.0+)
- Redux
- React-Redux
Features
- Page view tracking with custom variables
- Custom event tracking with custom variables
- Map redux state to custom variables
Installation
npm install --save react-redux-analytics
Getting Started
1. Register Middleware
import { applyMiddleware } from 'redux'
import { analyticsMiddleware } from 'react-redux-analytics'
import { analyticsPluginMiddleware } from 'react-redux-analytics-plugin'
const enhancer = applyMiddleware(...,
analyticsMiddleware({
reducerName: 'analytics',
...
}),
analyticsPluginMiddleware({...})
)
2. Register Reducer
import { createStore } from 'redux'
import { analyticsMiddleware } from 'react-redux-analytics'
const store = createStore(combineReducers({
'analytics': analyticsReducer,
...
}), initialState, enhancer);
3. Listen location change from history (optional)
import createHistory from 'history/createBrowserHistory'
import { createLocationSubscriber } from 'react-redux-analytics'
const history = createHistory()
const locationSubscriber = createLocationSubscriber(store)
history.listen((location) => {
locationSubscriber.notify(location, "replace")
})
4. Track Page View on componentDidMount
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { sendAnalytics } from 'react-redux-analytics'
class NewsPage extends React.Component {
}
export default compose(
connect({ ... }, { ... }),
sendAnalytics({
pageName: 'news:top',
prop10: '/news',
}),
)(IndexPage)
5. Track Custom Event on Click
import { sendEvent } from 'react-redux-analytics'
class NewsPage extends React.Component {
render(){
const { dispatch } = this.props
return(<div>
...
<div className="button" onClick={()=>{
dispatch(sendEvent({
events: ['event1'],
prop30: 'Click me',
eVar5: 'clicked',
}}>Click me</div>
...
</div>)
}
}
API
Higher-order component
sendAnalytics()
SendAnalytics HoC decorates a React component and provides it with the ability to dispatch SEND_PAGE_VIEW action when the component is mounted or updated.
sendAnalytics(options: {
sendPageViewOnDidMount?: boolean | ((props: Object, state: Object) => boolean);
sendPageViewOnDidUpdate?: boolean | (( prevProps: Object, props: Object, state: Object) => boolean);
mapPropsToVariables?: (props: Object, state: Object) => Object;
onDataReady?: boolean | ((props: Object, state: Object) => boolean);
snapshotPropsOnPageView? : boolean | ((props: Object, state: Object) => boolean);
mixins?: string[];
} & { [key:string]: any }) : (wrapped: React.Component) => React.Component
arguments
options
(Object)
-
[sendPageViewOnDidMount]
(boolean | ((props: Object, state: Object) => boolean))
If true
, the decorated component dispatches SEND_PAGE_VIEW
at componentDidMount
. If the argument is a function, the function is evaluated at componentDidMount
lifecycle and the action will be dispatched only if the result value is true
.
Default value: true
-
[sendPageViewOnDidUpdate]
(boolean | (( prevProps: Object, props: Object, state: Object) => boolean))
Similar to the above sendPageViewOnDidMount
. The component will dispatches SEND_PAGE_VIEW
at componentDidUpdate
if the value is true
or the result value of function is true
.
Default value: false
-
[mapPropsToVariables]
((props: Object, state: Object) => Object)
Accepts a function that maps props and redux state to tracking variables when the component dispatches SEND_PAGE_VIEW
. The mapped tracking variables are injected in variables
payload of SEND_PAGE_VIEW
.
Default value: undefined
-
[onDataReady]
(boolean | ((props: Object, state: Object) => boolean))
If a function is specified, the component will delay the dispach of SEND_PAGE_VIEW
until the result value of the function becomes true
. The function is first evaluated just after sendPageViewOnDidMount
and sendPageViewOnDidUpdate
is evaluated to be true. If the the result of this function is true
, the component dispatches action immediately. Otherwise, the function is evaluated at every componentWillReceiveProps
lifecycle until the result finally becomes true
. (Note: Evaluation of any succeeding sendPageViewOnDidUpdate
is skipped if the previous SEND_PAGE_VIEW
dispatch is delayed and the result of corresponding onDataReady
evaluation has not yet been true
)
Default value: true
-
[snapshotPropsOnPageView]
(boolean | ((props: Object, state: Object) => boolean))
Evaluated everytime the component dispatches SEND_PAGF_VIEW
. If the result is true
, it dispatches PAGE_SNAPSHOT_PROPS
to save the props (at the moment) in the page-scope of redux store. (Under [store root]
-> [analytics scope]
-> [page
] -> [snapshot
])
Default value: false
-
[mixins]
(string[])
Accpets an array of string to white-list keys of page-scope variables, global variables in the redux store, and global mapped variables. This array is merged with pageviewMixins
in analyticsMiddleware
, forming the concluding white-list. The analyticsMiddlware
injects whilte-listed variables into variables
of SEND_PAGE_VIEW
payload.
Default value: []
-
[staticVariables]
({ [key: string]: any })
The rest properties are regarded as static variables that are injected into everySEND_PAGE_VIEW
payload.
Default value: {}
Example
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { sendAnalytics } from 'react-redux-analytics'
import NewsPageArticleComponent from './presentation'
export default compose(
connect((state, { index })=>({
loaded: !state.article.loaded,
articleIndex: index,
pageIdx: state.pager.index,
title: state.article.body.title,
author: state.article.body.author,
conent: state.article.body.content,
}),
sendAnalytics({
sendPageViewOnDidMount: true,
sendPageViewOnDidUpdate: (prevProps, props) => prevProps.articleIndex !== props.articleIndex,
onDataReady: (props) => props.loaded,
mapPropsToVariables: (props) => ({
'prop5': props.title,
'prop10': props.articleIndex,
'eVar9': props.author,
}),
mixins: ['evar30', 'eVar31'],
pageName: 'ArticlePage',
channel: 'news',
}),
)(NewsPageArticleComponent)
Redux Middleware
analyticsMiddleware()
Middleware that transform SEND_PAGE_VIEW
and SEND_EVENT
action before they reaches to the reducer or plugin middlewares. It extends variables
property of the payload of each action by injecting values that come directly from analytics state of redux store, and the values that are mapped from entire redux store. The middleware can also override location
property of SEND_PAGE_VIEW
payload, and eventName
property of SEND_EVENT
payload.
analyticsMiddleware(options : {
reducerName?: string;
pageViewMixins?: string[];
eventMixins?: string[];
mapStateToVariables?: (state: Object) => Object;
getLocationInStore?: (state: Object) => Object;
composeEventName?: (composedVariables: Object, state: Object) => string | null;
suppressPageView?: (state: Object, transformedAciton: Object) => boolean,
}) : (store: Redux.Store) => (next: function) => (action: Object) => void
arguments
options
(Object)
-
[reducerName]
(string)
Specifies the name of this middleware's reducer ( that is the same as the name of analytics state in the redux store). This value has to be equal to the alias that you have guven toanalyticsReducer
incombineReducers
option.
Default value: 'analytics'
-
[pageViewMixins]
(string[])
Accpets an array of string. This array is merged with mixins
in SEND_PAGE_VIEW
payload, forming the final white-list of variables. The middleware selects white-listed variables from analytics state (that are consisted of page-scope variables and global-scope variables) and mapped variable (generated by mapStateToVariables
), and then injects them into variables
of SEND_PAGE_VIEW
payload.
Default value: []
-
[eventMixins]
(string[])
Similar to pageViewMixins
. This property is merged with mixins
in SEND_EVENT
payload, and is used to extend variables
property of SEND_EVENT
payload.
Default value: []
-
[mapStateToVariables]
((state: Object) => Object)
Accepts a function that maps the state of entire redux store to variables being used to extend variables
property of SEND_PAGE_VIEW
and SEND_EVENT
payload. Only values whose key are included in the mixin white-list are injected to the payload.
Default value: () => ({})
-
[getLocationInStore]
((state: Object) => Object)
Accepts a function that maps the state of entire redux store to a location
Object that is passed to reducer and plugin middlewares by SEND_PAGE_VIEW
action. The middleware overrides the location
property of the payload with the result of the function only if location
property of the original payload is null
and the return value is NOT null
.
Default value: () => null
-
[composeEventName]
((composedVariables: Object, state: Object) => string | null)
If a function is specified, the middleware use the return value of the function to override eventName
property of SEND_EVENT
payload. The function takes two arguments: copmosedVariables
(1st argument) that are resulting variables
composed of original payload variables
and injected values, and state
(2nd argument) that are the state of entire redux store. Like getLocationInStore
, the middleware overrides property only if original property is null
and the output value is NOT null
.
Default value: () => null
-
[suppressPageView]
((state: Object, transformedAction: Object) => boolean)
If a function is specified, analyticsMiddleware
evaluates it just before passing a transfromed SEND_PAGE_VIEW
action to the next middleware. If the return value is false, the action is discarded in this middleare and thus not be received by the plugin middlware (if it is properly placed after this one). This option is only useful if you cannot control dispatches of SEND_PAGE_VIEW
by the lifecycle hooks of sendAnalytics
.
Default value: () => false
Example
import { createStore, combineReducers, applyMiddleware } from 'redux'
import {analyticsReducer, analyticsMiddleware} from 'react-redux-analytics'
import { analyticsPluginMiddleware } from 'react-redux-analytics-plugin'
import { routerReducer, routerMiddleware } from 'react-router-redux'
import { articleReducer } from './reducers'
const enhancer = applyMiddleware(
routerMiddleware({
}),
analyticsMiddleware({
reducerName: 'analytics',
pageViewMixins: ['prop40', 'prop41'],
eventMixins: ['pageName', 'channel'],
mapStateToVariables: (state) => ({
prop40: state.routing.locationBeforeTransitions.pathname,
prop41: state.routing.locationBeforeTransitions.search,
}),
getLocationInStore: (state) => state.routing.locationBeforeTransitions,
composeEventName: (composedVariables, state) => `${composedVariables}/${composedVariables.events.join(',')}`,
}),
analyticsPluginMiddleware({
}),
)
export default (initialState = {}) => createStore(combineReducers({
analytics: analyticsReducer,
article: articleReducer,
routing: routerReducer
}), initialState, enhancer)
Utils
createLocationSubscriber()
Factory of a helper object (LocationSubscriber
) that you can use with react-router
.
locationSubscriber
subscribes changes of location
(, which must implement at least [pathname, search, hash]
properties of window.location
interface) from the routing framework, and changes page state of analytics middlewares in the redux store.
This helper is only useful if you want to utilize this middleware's complex page-scope variables management and historical page stack mechanism.
createLocationSubscriber(options: {
dispatch: (action: Object) => void;
}): { notify: (location: Object, action: 'pop'|'push'|'replace') => void }
arguments
options
(Object)
dispatch
(function)
Accepts the dispatch
function of Redux store.
return
locationSubscriber
(Object)
An object that has only one method notify
that subscribes changes of location from a routing framework.
-
notify()
If called, one of LOCATION_PUSH
, LOCATION_POP
, LOCATION_REPLACE
actions is dispatched depending on action
argument of this callback.
-
location
(Object) *required
Specifies a location object that specifies the location (pathname, search, hash) of next page.Supposed to be informed by the routing framework.
-
[action]
('pop'|'push'|'replace')
Accepts one of 'pop'
, 'push'
, or 'replace'
. If 'pop'
is specified, the locationSubscriber
dispatches LOCATION_POP
. And 'pop'
for LOCATION_PUSH
, 'replace'
for LOCATION_REPLACE
.
Example
import createHistory from 'history/createBrowserHistory'
import { createLocationSubscriber } from 'react-redux-analytics'
import createStore from './redux/createStore'
const store = createStore({})
const history = createHistory()
const locationSubscriber = createLocationSubscriber(store);
locationSubscriber.notify(history.location, 'pop')
const unlisten = history.listen((location, action) => {
locationSubscriber.notify(location, action);
});
Action Creators
The following action creators can be used to manually send tracks or to control the middleware's state.
sendPageView()
Creates SEND_PAGE_VIEW
action. The payload of SEND_PAGE_VIEW
is transformed in analyticsMiddlware
, and the transformed action is then relayed to plugin middlewares that finally creates server request to the tracking server. (Adobe analytics, GA, ...)
sendPageView(
location: Object = null,
mixins: string[] = [],
variables: { [key: string]: any } = {},
): Redux.Action
arguments
-
[location]
(Object)
Accepts a window.location
like object (specs is described above). If null
is passed, the analyticsMiddleware
tries to override the property by page.location
property of the state, or the result of getLocationInStore
option of the middleware. (by the respective order)
Default value: null
-
[mixins]
(string[])
Same as the mixin
property of sendAnalytics option
. If an array of string is specified, it is merged with pageViewMixins
in analyticsMiddleware
option to form a white-list of variables to be injected byanalyticsMiddlware
.
Default value: []
-
[variables]
({ [key: string]: any })
Specifies an object in which key
is the name of a variable, and the value
is the value assigned to the variable. These variables are merged with the injected variables by analyticsMiddleware
and is sent with a PageView track.
Default value: {}
sendEvent()
Creates SEND_EVENT
action. Like SEND_PAGE_VIEW
, analyticsMiddleware
transforms the payload of this action and relays it to plugin middlewares.
sendEvent(
variables: { [key: string]: any} = {},
mixins: string[] = [],
eventName: string = null,
): Redux.Action
arguments
-
[variables]
({ [key:string]: any})
Specifies variables to be sent with SEND_EVENT
in just the same manner as sendPageView
. The variables are merged with injected ones by analyticsMiddleware
and sent with an custom event track. (Custom link track in Adobe analytics)
Default value: {}
-
[mixins]
(string[])
Together with eventMixins
in analyticsMiddleware
, this array of string forms a white-list of variables that are injected to variables
payload of resulting SEND_EVENT
action.
Default value: []
-
[eventName]
(string)
An unique name that is used to distinguish or aggregate custom events by the tracking server. If not specified, analyticsMiddleware
tries to override the value by the result of composeEventName
function specified at the middleware's option.
pushLocation()
Creates a LOCATION_PUSH
action that pushes the current page-scope state to the top of internal page stack, and creates a new page-scope state.
pushLocation(
location: Object,
inherits: (boolean | string[]) = false,
variables: {[key:string]: any} = {}
)
arguments
-
location
(Object) *required
Specifies a window.location
like object. This value is stored in page-scope state of the middleware, and overrides the result of getLocationInStore
property of anallyticsMiddleware
option.
-
[inherits]
(boolean | string[])
Accepts a boolean value or a string array. If true, variables
property of the page-scope state is copied to the new page-scope state. If a string array is specified, only the variables whose name is included in the array are copied. Otherwise, the new page-scope state is created with a blank ({}
) variable set.
Defaul value: false
-
[variables]
({[key:string]: any})
If specified, the object is used as the initial value of variables
property of new page-scope state. If inherits
is not false, the inherited variables will be merged with this.
Default value: {}
popLocation()
Creates a LOCATION_POP
action that discards the current page-scope state and restore the one from the top of stack.
popLocation()
replaceLocation()
Creates a LOCATION_REPLACE
action. This action discards the current page-scope state, and creates a new page-scope state.
replaceLocation(
location: Object,
inherits: (boolean | string[]) = false,
variables: {[key:string]: any} = {}
)
arguments
-
location
(Object) *required
Same as location
in pushLocation
-
[inherits]
(boolean | string[])
Same as inherits
in pushLocation
-
[variables]
({[key:string]: any})
Same as variables
in pushLocation
updateGlobalVariables()
Creates a GLOBAL_VARIABLES_UPDATE
action that sets or updates variables
property of global-scope state, which is created with {}
value when reducer is initialized, and is persistent over LOCATION_CHANGE
, LOCATION_POP
, LOCATION_REPLACE
actions.
updateGlobalVariables(
variables: {[key: string]: any},
)
arguments
variables
({[key:string]: any}) *required
Specifies an object to update the variables
property of global-scope state. If a variables with the same key already exists in the state, the value is overriden by the one specified in the argument.
clearGlobalVariables()
Creates a GLOBAL_VARIABLES_CLEAR
action, that reset variables
property of global-scope state to {}
.
clearGlobalVariables()
updatePageVariables()
Creates a PAGE_VARIABLES_UPDATE
action. Like GLOBAL_VARIABLES_UPDATE
, this action sets or updates variables in Redux store. But they are stored in the variables
propety of page-scope state, so they will be cleared when any of LOCATION_PUSH
, LOCATION_POP
, LOCATION_REPLACE
actions is dispatched.
updatePageVariables(
variables: {[key: string]: any},
)
arguments
variables
({[key:string]: any}) *required
Specifies an object to be stored in page-scope state, just like variables
argument in globalVariablesUpdate()
to page-scope state.
clearPageVariables()
Creates a PAGE_VARIABLES_CLEAR
action that resets the variables
property of page-scope state. This is the counterpart to clearGlobalVariables()
, as updatePageVariables()
is to updateGlobalVariables()
.
clearPageVariables()