Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import track, { useTracking } from 'react-tracking';
- `dispatch`, which is a function to use instead of the default dispatch behavior. See the section on custom `dispatch()` later in this document.
- `dispatchOnMount`, when set to `true`, dispatches the tracking data when the component mounts to the DOM. When provided as a function will be called on componentDidMount with all of the tracking context data as the only argument.
- `process`, which is a function that can be defined once on some top-level component, used for selectively dispatching tracking events based on each component's tracking data. See more details later in this document.
- `forwardRef`, when set to `true`, adding a ref to the wrapped component will actually return the instance of the underlying component. Default is `false`.

#### `tracking` prop

Expand Down
33 changes: 33 additions & 0 deletions src/__tests__/e2e.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -866,4 +866,37 @@ describe('e2e', () => {
status: 'failed',
});
});

it('can access wrapped component by ref', async () => {
const focusFn = jest.fn();
@track({}, { forwardRef: true })
class Child extends React.Component {
focus = focusFn;

render() {
return 'child';
}
}

class Parent extends React.Component {
componentDidMount() {
this.child.focus();
}

render() {
return (
<Child
ref={el => {
this.child = el;
}}
/>
);
}
}

const parent = await mount(<Parent />);

expect(parent.instance().child).not.toBeNull();
expect(focusFn).toHaveBeenCalledTimes(1);
});
});
29 changes: 23 additions & 6 deletions src/withTrackingComponentDecorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ export const ReactTrackingContext = React.createContext({});

export default function withTrackingComponentDecorator(
trackingData = {},
{ dispatch = dispatchTrackingEvent, dispatchOnMount = false, process } = {}
{
dispatch = dispatchTrackingEvent,
dispatchOnMount = false,
process,
forwardRef = false,
} = {}
) {
return DecoratedComponent => {
const decoratedComponentName =
DecoratedComponent.displayName || DecoratedComponent.name || 'Component';

function WithTracking(props) {
function WithTracking({ rtFwdRef, ...props }) {
const { tracking } = useContext(ReactTrackingContext);
const latestProps = useRef(props);

Expand Down Expand Up @@ -118,20 +123,32 @@ export default function withTrackingComponentDecorator(
[getTrackingDispatcher, getTrackingDataFn, getProcessFn]
);

const propsToBePassed = useMemo(
() => (forwardRef ? { ...props, ref: rtFwdRef } : props),
[props, rtFwdRef]
);

return useMemo(
() => (
<ReactTrackingContext.Provider value={contextValue}>
<DecoratedComponent {...props} tracking={trackingProp} />
<DecoratedComponent {...propsToBePassed} tracking={trackingProp} />
</ReactTrackingContext.Provider>
),
[contextValue, props, trackingProp]
[contextValue, trackingProp, propsToBePassed]
);
}

WithTracking.displayName = `WithTracking(${decoratedComponentName})`;
if (forwardRef) {
const forwarded = React.forwardRef((props, ref) => (
<WithTracking {...props} rtFwdRef={ref} />
));
forwarded.displayName = `WithTracking(${decoratedComponentName})`;
hoistNonReactStatic(forwarded, DecoratedComponent);
return forwarded;
}

WithTracking.displayName = `WithTracking(${decoratedComponentName})`;
hoistNonReactStatic(WithTracking, DecoratedComponent);

return WithTracking;
};
}