I'd like to implement a higher order react component which can be used to easily track events (like a click) on any React component. The purpose of this is to easily hook clicks (and other events) into our first party analytics tracker.
The challenge I've come across is that the React synthetic event system requires events (like onClick
) be bound to react DOM elements, like a div
. If the component I'm wrapping is a custom component, like every HOC implemented via a higher order function is, my click events don't bind correctly.
For example, using this HOC, the onClick
handler will fire for button1, but not for button2.
// Higher Order Component
class Track extends React.Component {
onClick = (e) => {
myTracker.track(props.eventName);
}
render() {
return React.Children.map(
this.props.children,
c => React.cloneElement(c, {
onClick: this.onClick,
}),
);
}
}
function Wrapper(props) {
return props.children;
}
<Track eventName={'button 1 click'}>
<button>Button 1</button>
</Track>
<Track eventName={'button 2 click'}>
<Wrapper>
<button>Button 2</button>
</Wrapper>
</Track>
CodeSandbox with working example: https://codesandbox.io/embed/pp8r8oj717
My goal is to be able to use an HOF like this (optionally as a decorator) to track clicks on any React component definition.
export const withTracking = eventName => Component => props => {
return (
<Track eventName={eventName}>
{/* Component cannot have an onClick event attached to it */}
<Component {...props} />
</Track>
);
};
The only solution I can think of atm is using a Ref
on each child and manually binding my click event once the Ref
is populated.
Any ideas or other solutions are appreciated!
UPDATE:
Using the remapChildren
technique from @estus' answer and a more manual way of rendering the wrapped components, I was able to get this to work as a higher order function - https://codesandbox.io/s/3rl9rn1om1
export const withTracking = eventName => Component => {
if (typeof Component.prototype.render !== "function") {
return props => <Track eventName={eventName}>{Component(props)}</Track>;
}
return class extends Component {
render = () => <Track eventName={eventName}>{super.render()}</Track>;
};
};
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…