Skip to content

Commit

Permalink
Add new useTracking react hook
Browse files Browse the repository at this point in the history
  • Loading branch information
damassi committed May 20, 2019
1 parent 0a62f0f commit f0ffffc
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 15 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ export default track({
})(FooPage);
```

### Usage with React Hooks

Following the example above, once a component is wrapped with `track` we can access a `tracking` object via the `useTracking` hook from anywhere in the sub-tree:

```js
import { useTracking } from 'react-tracking'

const SomeChild = props => {
const tracking = useTracking()

<div
onClick={() => {
tracking.trackEvent({ action: 'click' });
}}
/>
}
```

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
Expand Down
41 changes: 30 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"peerDependencies": {
"core-js": "2.x",
"react": "^16.3",
"react": "^16.8",
"prop-types": "^15.x"
},
"devDependencies": {
Expand Down
62 changes: 59 additions & 3 deletions src/__tests__/e2e.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable react/destructuring-assignment,react/no-multi-comp,react/prop-types,react/prefer-stateless-function */
import React from 'react';
import React, { useContext } from 'react';
import { mount } from 'enzyme';

const dispatchTrackingEvent = jest.fn();
Expand All @@ -12,13 +12,13 @@ const testState = { booleanState: true };

describe('e2e', () => {
// eslint-disable-next-line global-require
const track = require('../').default;
const { default: track, useTracking, ReactTrackingContext } = require('../');

beforeEach(() => {
jest.clearAllMocks();
});

it('defaults moslty everything', () => {
it('defaults mostly everything', () => {
@track(null, { process: () => null })
class TestDefaults extends React.Component {
render() {
Expand Down Expand Up @@ -584,4 +584,60 @@ describe('e2e', () => {
page: 'Page',
});
});

it('root context items are accessible to children', () => {
const App = track()(() => {
return <Child />;
});

const Child = () => {
const trackingContext = useContext(ReactTrackingContext);
expect(Object.keys(trackingContext.tracking)).toEqual([
'data',
'dispatch',
'process',
]);
return <div />;
};

mount(<App />);
});

it('dispatches tracking events from a useTracking hook tracking object', () => {
const outerTrackingData = {
page: 'Page',
};

const Page = track(outerTrackingData, { dispatch })(props => {
return props.children;
});

const Child = () => {
const tracking = useTracking();

expect(tracking.getTrackingData()).toEqual(outerTrackingData);

return (
<button
type="button"
onClick={() => {
tracking.trackEvent({ event: 'buttonClick' });
}}
/>
);
};

const wrappedApp = mount(
<Page>
<Child />
</Page>
);

wrappedApp.find('button').simulate('click');

expect(dispatch).toHaveBeenCalledWith({
...outerTrackingData,
event: 'buttonClick',
});
});
});
55 changes: 55 additions & 0 deletions src/__tests__/useTracking.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { mount } from 'enzyme';
import React from 'react';
import { renderToString } from 'react-dom/server';
import track from '../withTrackingComponentDecorator';
import useTracking from '../useTracking';

describe('useTracking', () => {
it('throws error if tracking context not present', () => {
const ThrowMissingContext = () => {
useTracking();
return <div>hi</div>;
};
try {
renderToString(<ThrowMissingContext />);
} catch (error) {
expect(error.message).toContain(
'Attempting to call `useTracking` without a ReactTrackingContext present'
);
}
});

it('dispatches tracking events from a useTracking hook tracking object', () => {
const outerTrackingData = {
page: 'Page',
};

const dispatch = jest.fn();

const App = track(outerTrackingData, { dispatch })(() => {
const tracking = useTracking();

expect(tracking.getTrackingData()).toEqual({
page: 'Page',
});

return (
<button
type="button"
onClick={() =>
tracking.trackEvent({
event: 'buttonClick',
})
}
/>
);
});

const wrapper = mount(<App />);
wrapper.simulate('click');
expect(dispatch).toHaveBeenCalledWith({
...outerTrackingData,
event: 'buttonClick',
});
});
});
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export {
} from './withTrackingComponentDecorator';
export { default as trackEvent } from './trackEventMethodDecorator';
export { default as TrackingPropType } from './TrackingPropType';
export { default as useTracking } from './useTracking';
export { default } from './trackingHoC';
29 changes: 29 additions & 0 deletions src/useTracking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import merge from 'deepmerge';
import { useContext, useCallback } from 'react';
import { ReactTrackingContext } from './withTrackingComponentDecorator';

export default function useTracking() {
const trackingContext = useContext(ReactTrackingContext);

if (!(trackingContext && trackingContext.tracking)) {
throw new Error(
'Attempting to call `useTracking` ' +
'without a ReactTrackingContext present. Did you forget to wrap the top of ' +
'your component tree with `track`?'
);
}

const trackEvent = useCallback(
data => {
return trackingContext.tracking.dispatch(
merge(trackingContext.tracking.data, data)
);
},
[trackingContext.tracking.data]
);

return {
getTrackingData: () => trackingContext.tracking.data,
trackEvent,
};
}

0 comments on commit f0ffffc

Please sign in to comment.