I had a problem with higher-order components, their use of even higher-order components, passing styles from the component-to-be-wrapped to the HOC, and testing of the bare/naked/base HOC without involving the wrapped component. In this post I shortly present the solution my colleague and I found to be working quite nicely (I expect you know all the terms/names used in this post). What I wanted:
- Create a higher-order component (WithBaseEditor) that has its own styles (using withStyles from material-ui/JSS)
- Be able to override styles in the HOC from the wrapped component (ImageEditor/VideoEditor/ArticleEditor)
- Be able to easily test the HOC methods separated from the wrapped component
// BaseEditor.js const baseEditorStyles = {...}; const WithBaseEditor = (Component, options = {}, overrideStyles = {}) => { class BaseEditor extends React.Component { ... baseEditorMethod() {} // this is "private" mutualMethod() {} ... render() { return ( // some mutual jsx for layout <Component mutualMethod={this.mutualMethod} /> ); } } const enhanced = withStyles(overrideStyles)(withStyles(baseEditorStyles)(BaseEditor)); enhanced.__Naked = BaseEditor; return enhanced; }; export default WithBaseEditor; // ImageEditor.js import WithBaseEditor from 'BaseEditor'; const styles = {...}; class ImageEditor extends React.Component {...} export default WithBaseEditor(withStyles(styles)(ImageEditor), options, styleConfig);
What happens in the code:
Create a HOC called WithBaseEditor that is implemented as a function that takes a component, some options, and styles. In the function body create a new class extending React.Component that implements what I call a BaseEditor that has got some private logic and some mutual logic. Private logic is never to be passed down to the component that is being wrapped whereas mutual logic is passed explicitly in the props. In order to use isolated styles for the BaseEditor, they’re defined in the HOC’s own file. In order to have some styling in the BaseEditor to be overwritten with customised styles “coming from” the wrapped component, they’re passed into the HOC function. What the HOC returns, finally, is BaseEditor, that is first wrapped with its own isolated styles defined in the same file and then re-wrapped with the styles passed in from the caller of the HOC. This way, the wrapped component, ImageEditor, can define its own styles and some styles that it wants to overwrite in the BaseEditor, and just call the HOC with carefully chosen arguments.
What’s that with the __Naked = BaseEditor?
Immediately before returning the enhanced component the actual BaseEditor class is injected into the returned component. This way BaseEditor is available bare/naked/whatever when you want to test it without involving wrapped component logic. You just pass in some dummy wrapped component and write the tests for the BaseEditor’s private methods.