Kevin Suttle Kevin Suttle - 1 month ago 8
Javascript Question

Inject new inline styles to a React HOC product

Using a common HOC pattern like so works beautifully. However, there are times when you really don't want a component to be wrapped and just want the same component you passed in extended a bit. That's what I'm struggling with here.

Wrapper HOC

const flexboxContainerStyles = {
display: 'flex',
flexDirection: 'row',
backgroundColor: 'pink',
}

let WrapInFlexContainer = FlexChild => class extends React.Component {
render(){

return (
<div className="flexContainer" style={flexboxContainerStyles} >
<FlexChild {...this.props} />
</div>
)
}
}

const Button = (props) => <button>{props.txt}</button>
let FlexButton = WrapInFlexContainer(Button);


The following examples result in a button with no style attributes.

Example 1.1: pass-through via createClass

function hocPassThroughViaClass(Component) {
return React.createClass({
render: function() {
return <Component {...this.props} style={flexboxContainerStyles}/>;
}
});
}


Example 1.2 pass-through via direct render

let hocPassThroughViaRender = Element => class extends React.Component {
render(){
return <Element {...this.props} className="flexContainer" style={flexboxContainerStyles} />
}
}


Example 2: create

function hocCreate(Component) {
return React.createClass({
render: function() {
const modifiedProps = Object.assign({}, {...this.props}, {...flexboxContainerStyles});
return React.createElement(Component, { ...modifiedProps });
}
});
}


Example 3: clone

// 2.
function hocClone(Component) {
return React.createClass({
render: function() {
const modifiedProps = Object.assign({}, {...this.props}, {...flexboxContainerStyles});
return React.cloneElement(<Component {...modifiedProps } />);
}
});
}

// render examples
let HOCPassThroughViaClassButton = hocPassThroughViaClass(Button); // 1.1
let HOCPassThroughRenderButton = hocPassThroughViaRender(Button); // 1.2
let HOCCreatedButton = hocCreate(Button); // 2
let HOCClonedButton = hocClone(Button); // 3


From a couple of points I'm seeing here and there across the web, it doesn't seem like it's possible to return the same
Component
if it is an only child.

See: https://github.com/threepointone/glamor/blob/master/docs/createElement.md

https://discuss.reactjs.org/t/trying-to-do-a-reactdom-render-a-el-replacing-the-el-not-appending-to-it/2681/2

Answer

The following examples result in a button with no style attributes.

Isn't this happening because you're not passing the style prop along? Wouldn't this be fixed by doing this:

const Button = (props) => <button style={props.style}>{props.txt}</button>

Update:

HOCs don't magically apply props to children of the wrapped component, meaning that low level elements like <button /> or <div /> need props passed to them one way or another. You're passing props to <Button />, not <button />. You can however make an HOC that takes a basic element and adds whatever to it.

let hoc = element => (
  class extends React.Component {
    render() {
      let { children, ...props } = this.props
      return React.createElement(
        element, 
        { ...props, style: flexboxContainerStyles },
        children,
      )
    }
  }
)

Usage:

let FlexButton = hoc('button')

let App = props => <FlexButton>{props.txt}</FlexButton>

fiddle

That being said, you're not changing the API of the base component by passing along known props like style and className along. In fact it's a great way to make components more reusable without specifying implementation details.

// good!
let Button = ({ children, ...props }) => <button {...props}>{children}</button>