Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new useTracking React hook #124

Merged
merged 2 commits into from
May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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": "3.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',
});
});
});
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export {
default as withTracking,
ReactTrackingContext,
TrackingContextType,
} from './withTrackingComponentDecorator';
export { default as trackEvent } from './trackEventMethodDecorator';
export { default as TrackingPropType } from './TrackingPropType';
export { default as useTracking } from './useTracking';
damassi marked this conversation as resolved.
Show resolved Hide resolved
export { default } from './trackingHoC';
26 changes: 26 additions & 0 deletions src/useTracking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import merge from 'deepmerge';
import { useContext, useMemo } 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`?'
tizmagik marked this conversation as resolved.
Show resolved Hide resolved
);
}

return useMemo(
() => ({
getTrackingData: () => trackingContext.tracking.data,
trackEvent: data =>
trackingContext.tracking.dispatch(
merge(trackingContext.tracking.data, data)
),
}),
[trackingContext.tracking.data]
);
}