Skip to content

Commit 0cdb69f

Browse files
authored
Pass previous state to event definitions (#117)
1 parent ce1a5a9 commit 0cdb69f

9 files changed

+93
-29
lines changed

docs/api/event-definition.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
### `EventDefinition`
22

33
A `function` used by Redux Beacon to generate an event or series of events for a
4-
given Redux action. An event definition receives the associated action object,
5-
and the state of the application (before the action).
4+
given Redux action. An event definition receives the associated action object
5+
and the state of the application (before and after the action).
6+
7+
```typescript
8+
export type EventDefinition = (
9+
action: Action,
10+
prevState: any,
11+
nextState: any
12+
) => any | Array<any>;
13+
```
614

715
#### A Basic Event Definition
816
```js
9-
function (action, prevState) {
17+
function (action, prevState, nextState) {
1018
return {
1119
hitType: 'pageview',
1220
route: action.payload.location.pathname,
1321
referrer: prevState.currentRoute,
22+
numUserActions: nextState.numUserActions,
1423
};
1524
}
1625
```

docs/notes-on-unit-testing.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ transformations it's not a bad idea to test them. Or better yet to
55
start out with a test or two, and build out your event definitions to
66
make the tests pass.
77

8-
To help with testing, Redux-Beacon exposes a function `createEvents`
8+
To help with testing, Redux Beacon exposes a function `createEvents`
99
which it uses internally to create events from event
1010
definitions. `createEvents` has the following signature:
1111

1212
```js
13-
function createEvents(
14-
eventDefinition: EventDefinition | Array<EventDefinition>,
13+
export function createEvents(
14+
eventDefinition: EventDefinition,
1515
prevState: any,
1616
action: any,
17+
nextState: any
1718
): Array<any>;
1819
```
1920

index.d.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ interface Action {
66
}
77

88
/**
9-
* Used by Redux-Beacon to generate an event or series of events for a
10-
* given Redux action. An event definition is a function that will
11-
* receive the state of the application (before the action), and the
12-
* associated action object. The function should return an event, or
13-
* an array of events.
9+
* Used by Redux Beacon to generate an event or series of
10+
* events for a given Redux action. An event definition receives the
11+
* associated action object and the state of the application (before
12+
* and after the action).
1413
*/
15-
export type EventDefinition = (action: Action, prevState: any) => any | Array<any>;
14+
export type EventDefinition = (
15+
action: Action,
16+
prevState: any,
17+
nextState: any
18+
) => any | Array<any>;
1619

1720
/**
1821
* A map between your actions and your analytics events. Each key
@@ -49,7 +52,8 @@ export function createMetaReducer(
4952
* Use this function for testing your event definitions.
5053
*/
5154
export function createEvents(
52-
eventDefinition: EventDefinition | Array<EventDefinition>,
55+
eventDefinition: EventDefinition,
5356
prevState: any,
54-
action: any
57+
action: any,
58+
nextState: any
5559
): Array<any>;

src/main/__tests__/create-events.test.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,24 @@ describe('createEvents(eventDef, prevState, action)', () => {
8080
{ hitType: 'timing' },
8181
],
8282
},
83+
{
84+
title: 'event definition uses previous state',
85+
eventDef: (action, prevState, nextState) => ({
86+
hitType: 'event',
87+
numActions: nextState.numActions,
88+
}),
89+
nextState: { numActions: 3 },
90+
expected: [{
91+
hitType: 'event',
92+
numActions: 3,
93+
}],
94+
},
8395
].forEach((scenario, index) => {
8496
const {
8597
title,
8698
eventDef,
8799
prevState,
100+
nextState,
88101
action,
89102
expected,
90103
} = scenario;
@@ -93,7 +106,13 @@ describe('createEvents(eventDef, prevState, action)', () => {
93106
if (title === undefined || eventDef === undefined || expected === undefined) {
94107
throw new Error('tests require title, eventDef, and expected keys');
95108
}
96-
expect(createEvents(eventDef, prevState || {}, action || {})).toEqual(expected);
109+
const events = createEvents(
110+
eventDef,
111+
prevState || {},
112+
action || {},
113+
nextState || {}
114+
);
115+
expect(events).toEqual(expected);
97116
});
98117
});
99118
});

src/main/__tests__/integration.test.js

+22-5
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,39 @@ function runIntegrationTests(title, prepareStore) {
66
describe('When an action with an associated event definition is dispatched', () => {
77
it('creates the event and pushes it to the target', () => {
88
const eventsMap = {
9-
ROUTE_CHANGED: (action, prevState) => ({
9+
ROUTE_CHANGED: (action, prevState, nextState) => ({
1010
hitType: 'pageview',
1111
page: action.payload,
1212
referrer: prevState.route,
13+
numActions: nextState.numActions,
1314
}),
1415
};
1516
const target = jest.fn();
1617

17-
const reducer = (state = { route: '/home' }) => state;
18+
const initialState = {
19+
route: '/home',
20+
numActions: 0,
21+
};
22+
const reducer = (state = initialState, action) => {
23+
if (action.type === 'ROUTE_CHANGED') {
24+
return Object.assign({}, state, {
25+
route: action.payload,
26+
numActions: state.numActions + 1,
27+
});
28+
}
29+
return state;
30+
};
31+
1832
const store = prepareStore(reducer, eventsMap, target);
1933

2034
store.dispatch({ type: 'ROUTE_CHANGED', payload: '/my-account' });
2135

22-
expect(target).toHaveBeenCalledWith([
23-
{ hitType: 'pageview', page: '/my-account', referrer: '/home' },
24-
]);
36+
expect(target).toHaveBeenCalledWith([{
37+
hitType: 'pageview',
38+
page: '/my-account',
39+
referrer: '/home',
40+
numActions: 1,
41+
}]);
2542
});
2643
});
2744

src/main/__tests__/main.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ const target = () => {};
1919
createMiddleware(eventsMap, target, { logger, offlineStorage: offlineWeb });
2020
createMetaReducer(eventsMap, target, { logger, offlineStorage: offlineWeb });
2121

22-
let events: Array<any>
23-
events = createEvents(eventDefinition, {}, {});
24-
events = createEvents([eventDefinition, eventDefinition], {}, {});
22+
let events: Array<any>;
23+
events = createEvents(eventDefinition, {}, {}, {});

src/main/create-events.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const createEvents = (eventDefinition, prevState, action) =>
2-
[].concat(eventDefinition(action, prevState)).filter(ifTruethy => ifTruethy);
1+
const createEvents = (eventDefinition, prevState, action, nextState) =>
2+
[].concat(eventDefinition(action, prevState, nextState)).filter(ifTruethy => ifTruethy);
33

44
module.exports = createEvents;

src/main/create-meta-reducer.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@ const createEvents = require('./create-events');
22
const registerEvents = require('./register-events');
33

44
function createMetaReducer(eventDefinitionsMap, target, extensions = {}) {
5+
/* Why not arrow functions? AOT... */
56
/* eslint-disable func-names */
67
return function (reducer) {
78
return function (prevState, action) {
89
if (!eventDefinitionsMap[action.type]) {
910
return reducer(prevState, action);
1011
}
1112

12-
const events = createEvents(eventDefinitionsMap[action.type], prevState, action);
13+
const nextState = reducer(prevState, action);
14+
const events = createEvents(
15+
eventDefinitionsMap[action.type],
16+
prevState,
17+
action,
18+
nextState
19+
);
20+
1321
registerEvents(events, target, extensions, prevState, action);
1422

15-
return reducer(prevState, action);
23+
return nextState;
1624
};
1725
};
1826
}

src/main/create-middleware.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ function createMiddleware(eventDefinitionsMap, target, extensions = {}) {
88
}
99

1010
const prevState = store.getState();
11+
const result = next(action);
12+
const nextState = store.getState();
1113

12-
const events = createEvents(eventDefinitionsMap[action.type], prevState, action);
14+
const events = createEvents(
15+
eventDefinitionsMap[action.type],
16+
prevState,
17+
action,
18+
nextState
19+
);
1320

1421
registerEvents(events, target, extensions, prevState, action);
1522

16-
return next(action);
23+
return result;
1724
};
1825
}
1926

0 commit comments

Comments
 (0)