Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.js: Wrapping one component into another

Try:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          {this.props.children}
        after
      </div>
    );
  }
});

See Multiple Components: Children and Type of the Children props in the docs for more info.


Using children

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = ({name}) => (
  <Wrapper>
    <App name={name}/>
  </Wrapper>
);

render(<WrappedApp name="toto"/>,node);

This is also known as transclusion in Angular.

children is a special prop in React and will contain what is inside your component's tags (here <App name={name}/> is inside Wrapper, so it is the children

Note that you don't necessarily need to use children, which is unique for a component, and you can use normal props too if you want, or mix props and children:

const AppLayout = ({header,footer,children}) => (
  <div className="app">
    <div className="header">{header}</div>
    <div className="body">{children}</div>
    <div className="footer">{footer}</div>
  </div>
);

const appElement = (
  <AppLayout 
    header={<div>header</div>}
    footer={<div>footer</div>}
  >
    <div>body</div>
  </AppLayout>
);

render(appElement,node);

This is simple and fine for many usecases, and I'd recommend this for most consumer apps.


render props

It is possible to pass render functions to a component, this pattern is generally called render prop, and the children prop is often used to provide that callback.

This pattern is not really meant for layout. The wrapper component is generally used to hold and manage some state and inject it in its render functions.

Counter example:

const Counter = () => (
  <State initial={0}>
    {(val, set) => (
      <div onClick={() => set(val + 1)}>  
        clicked {val} times
      </div>
    )}
  </State>
); 

You can get even more fancy and even provide an object

<Promise promise={somePromise}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise>

Note you don't necessarily need to use children, it is a matter of taste/API.

<Promise 
  promise={somePromise}
  renderLoading={() => <div>...</div>}
  renderSuccess={(data) => <div>{data.something}</div>}
  renderError={(e) => <div>{e.message}</div>}
/>

As of today, many libraries are using render props (React context, React-motion, Apollo...) because people tend to find this API more easy than HOC's. react-powerplug is a collection of simple render-prop components. react-adopt helps you do composition.


Higher-Order Components (HOC).

const wrapHOC = (WrappedComponent) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

render(<WrappedApp name="toto"/>,node);

An Higher-Order Component / HOC is generally a function that takes a component and returns a new component.

Using an Higher-Order Component can be more performant than using children or render props, because the wrapper can have the ability to short-circuit the rendering one step ahead with shouldComponentUpdate.

Here we are using PureComponent. When re-rendering the app, if the WrappedApp name prop does not change over time, the wrapper has the ability to say "I don't need to render because props (actually, the name) are the same as before". With the children based solution above, even if the wrapper is PureComponent, it is not the case because the children element is recreated everytime the parent renders, which means the wrapper will likely always re-render, even if the wrapped component is pure. There is a babel plugin that can help mitigate this and ensure a constant children element over time.


Conclusion

Higher-Order Components can give you better performance. It's not so complicated but it certainly looks unfriendly at first.

Don't migrate your whole codebase to HOC after reading this. Just remember that on critical paths of your app you might want to use HOCs instead of runtime wrappers for performance reasons, particularly if the same wrapper is used a lot of times it's worth considering making it an HOC.

Redux used at first a runtime wrapper <Connect> and switched later to an HOC connect(options)(Comp) for performance reasons (by default, the wrapper is pure and use shouldComponentUpdate). This is the perfect illustration of what I wanted to highlight in this answer.

Note if a component has a render-prop API, it is generally easy to create a HOC on top of it, so if you are a lib author, you should write a render prop API first, and eventually offer an HOC version. This is what Apollo does with <Query> render-prop component, and the graphql HOC using it.

Personally, I use both, but when in doubt I prefer HOCs because:

  • It's more idiomatic to compose them (compose(hoc1,hoc2)(Comp)) compared to render props
  • It can give me better performances
  • I'm familiar with this style of programming

I don't hesitate to use/create HOC versions of my favorite tools:

  • React's Context.Consumer comp
  • Unstated's Subscribe
  • using graphql HOC of Apollo instead of Query render prop

In my opinion, sometimes render props make the code more readable, sometimes less... I try to use the most pragmatic solution according to the constraints I have. Sometimes readability is more important than performances, sometimes not. Choose wisely and don't bindly follow the 2018 trend of converting everything to render-props.


In addition to Sophie's answer, I also have found a use in sending in child component types, doing something like this:

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});