• Use a Render Prop!
  • By Michael Jackson
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: yoyoyohamapi
  • Proofreader: Mechanicianusey95

Update: I submitted a PR to React documentation and added Render props to it.

Update 2: Add a section to explain that “children as a function” is the same concept, just a different prop name.


A few months ago, I tweeted:

I can use a Render prop on a normal component to do what HOC can do. Refuse to argue.

I think the high-order component pattern, a popular means of reusing code in many React code, can be replaced 100% by a generic component with render Prop. The phrase “disagree to argue” was a friendly “jibe” to my friends in the React community, and a good series of discussions ensued, but in the end, I was disappointed that I couldn’t fully describe what I wanted to say in 140 characters. I’ve decided to write a longer article at some point in the future to explore this topic fairly and impartially.

When Tyler invited me to speak at Phoenix ReactJS two weeks ago, I thought it was time to explore this further. I had already arrived in Phoenix that week to launch our React Basics and Advanced tutorial, and I heard good news about the conference from my business partner Ryan, who spoke in April.

At the conference, my speech seemed a bit clickbait: Don’t write another HOC. You can watch my talk on Phoenix ReactJS ‘official YouTube channel or via the embedded video below:

If you don’t want to watch the video, you can read the main content of the talk below. But seriously: the video is much funnier 😀.

If you skip the video and start reading but don’t get what I’m saying, go back to the video. The presentation will be more detailed.

The problem with Mixins

I start with the main problem that higher-order components solve: code reuse.

Let’s go back to 2015 when we used React. CreateClass. Suppose you now have a simple React application that needs to track and display mouse positions in real time on the page. You might build an example like this:

import React from 'react'
import ReactDOM from 'react-dom'

const App = React.createClass({
  getInitialState() {
    return { x: 0.y: 0 }
  },

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  },

  render() {
    const { x, y } = this.state

    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})

ReactDOM.render(<App/>.document.getElementById('app'))
Copy the code

Now, suppose we also need to track the mouse position in another component. Can we reuse the code in

?

In the createClass paradigm, code reuse is addressed through techniques called “mixins.” Let’s create a MouseMixin that allows anyone to track mouse position.

import React from 'react'
import ReactDOM from 'react-dom'

// Mixin contains boilerplate code that you need to track mouse position in any application.
// We can put boilerplate code into a mixin so that other components can share it
const MouseMixin = {
  getInitialState() {
    return { x: 0.y: 0 }
  },

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }
}

const App = React.createClass({
  // Use mixin!
  mixins: [ MouseMixin ],
  
  render() {
    const { x, y } = this.state

    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})

ReactDOM.render(<App/>.document.getElementById('app'))
Copy the code

Problem solved, right? Now, anyone can easily mix MouseMixin into their component and get the X and Y coordinates of the mouse via the this.state property.

HOC is the new Mixin

Last year, with the arrival of the ES6 class, the React team finally decided to use the ES6 class instead of createClass. This is a wise decision; no one is going to maintain their own class model when JavaScript has classes built in.

There’s just one problem: ES6 classes don’t support mixins. In addition to not being part of the ES6 specification, Dan has discussed other issues with mixins in detail in a React blog post.

The problem with Minxins boils down to this

  • ES6 class. It does not support mixins.
  • Not direct enough. Minxins change states, so it’s hard to know where some states come from, especially if there’s more than one mixins.
  • Name conflict. Two mixins updating the same segment of state may overwrite each other.createClassThe API will handle both mixinsgetInitialStateCheck whether they have the same key. If they do, a warning will be issued, but this method is not reliable.

So, instead of mixins, many developers in the React community have finally decided to use higher-order components (HOC for short) for code reuse. Under this paradigm, code is shared through a decorator-like technique. First, you have a component that defines a number of tags that need to be rendered, and then you wrap it with several components that have the behavior that you want to share. So you are now decorating your components instead of mixing in the behavior you need!

import React from 'react'
import ReactDOM from 'react-dom'

const withMouse = (Component) = > {
  return class extends React.Component {
    state = { x: 0.y: 0 }

    handleMouseMove = (event) = > {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }

    render() {
      return (
        <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
          <Component {. this.props} mouse={this.state}/>
        </div>}}} const App = react.createclass ({render() {// Now we have a prop for the mouse position, State const {x, y} = this.props. Mouse return (const {x, y} = this.props.<div style={{ height: '100'}} % >
        <h1>The mouse position is ({x}, {y})</h1>
      </div>// Mouse prop const AppWithMouse = withMouse(App) reactdom.render ()<AppWithMouse/>, document.getElementById('app'))
Copy the code

Let’s say goodbye to Mixin and embrace HOC.

HOC is indeed an elegant solution to code reuse in the new era of ES6 classes, and it has been widely adopted by the community.

At this point, I want to ask: What drives us to move to HOC? Have we solved the problems we encountered with mixins?

Let’s take a look:

  • ES6 class. This is no longer an issue; ES6 classes create components that can be combined with HOC.
  • Not direct enough. Even with HOC, the problem remains. In mixin, we don’t know where state comes from, in HOC, we don’t know where props comes from.
  • Name conflict. We will still face this problem. Two HOC using a prop of the same name will collide and overwrite each other, and the problem will be more subtle this time because React won’t warn if the prop has the same name.

Another problem with both HOC and mixin is that both use static composition rather than dynamic composition. Ask yourself: In the HOC paradigm, where is composition happening? When the component class (AppWithMouse in the example above) is created, a static composition occurs.

You can’t use mixin or HOC in the Render method, which is key to the React dynamic composition model. Once you’ve finished composing in Render, you can take advantage of all the React lifetimes. Dynamic composition may be trivial, but maybe someday there will be a blog about it. Wait, I’m digressing. 😅

In summary: HOC created using ES6 classes still suffers from the same problems as createClass, which counts only as a refactoring.

Now stop talking about embracing HOC, we’re just embracing the new mixin! 🤗

In addition to the drawbacks mentioned above, HOC introduces a lot of red tape because it essentially wraps up components and creates a mixin substitute for existing components. The component returned from HOC needs to behave as much as possible like the component it wraps (it needs to receive the same props as the wrapped component, and so on). This fact makes building robust HOC require a lot of Boilerplate code.

For example, withRouter HOC in React Router, you can see props pass, wrappedComponentRef, hoist for wrapped components, and so on. When you need to add HOC to your React, you have to write them.

Render Props

Now, there’s another technique for code reuse that circumvents the mixin and HOC issues. In React Training, this is called “Render Props”.

I first met Render Prop at ChengLou’s React Europe talk on React – Motion, The

API he mentions makes a component share interpolated animation with its parent. If I were to define Render Prop, I would define it like this:

A Render Prop is a prop of type function that lets the component know what to render.

More colloquially: Instead of sharing component behavior by “mixing” or decorating, a normal component requires only a function prop to do some state sharing.

Continuing with the above example, we’ll simplify withMouse HOC into a normal

component with a Prop of render type function. Then, in the Render method of

, we can use a Render Prop to let the component know how to render:

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

// Unlike HOC, we can use a generic component with Render Prop to share code
class Mouse extends React.Component {
  static propTypes = {
    render: PropTypes.func.isRequired
  }

  state = { x: 0.y: 0 }

  handleMouseMove = (event) = > {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>)}}const App = React.createClass({
  render() {
    return (
      <div style={{ height: '100'}} % >
        <Mouse render={({ x.y}) = >// Render Prop gives us the state we need to render what we want<h1>The mouse position is ({x}, {y})</h1>)} / ></div>
    )
  }
})

ReactDOM.render(<App/>, document.getElementById('app'))
Copy the code

The clear concept here is that the

component actually calls its Render method to expose its state to the

component. So it’s nice that

can use this state however it wants. 😎


Here, I would like to make it clear that “children as a function” is exactly the same concept, except that the Render Prop is replaced with children prop. I’m not talking about a prop named Prop, I’m talking about the concept that you use a prop to render.

The technology circumvents all the problems that mixins and HOC face:

  • ES6 class. No problem, we can use Render Prop in components created by the ES6 class.
  • Not direct enough. We don’t have to worry about where the state or props are coming from anymore. We can see through the Parameter list of Render Prop which states or props are available.
  • Name conflict. There will now be no automatic attribute name merging, so there will be no room for name conflicts.

Also, Render Prop doesn’t introduce any red tape because you don’t wrap and decorate other components. It’s just a function! If you use TypeScript or Flow, you’ll find that it’s now easier to write a type definition for your render Prop component than HOC. Of course, that’s a topic for another day.

Plus, the composite model here is dynamic! Each combination happens inside Render, so we can take advantage of the React lifecycle and the natural flow of props and state.

Using this pattern, you can replace any HOC with a generic component with Render Prop. We can prove it! 😅

Render Props > HOCs

A more powerful piece of evidence that Render Prop is more powerful than HOC is that any HOC can be replaced with Render Prop, and vice versa. The following code shows withMouse HOC implemented using a generic

component with Render Prop:

const withMouse = (Component) = > {
  return class extends React.Component {
    render() {
      return<Mouse render={mouse => ( <Component {... this.props} mouse={mouse}/> )}/> } } }Copy the code

Aware readers are probably aware that withRouter HOC is indeed implemented in the React Router code base with a Render prop **!

So you’re not interested? Go use Render Prop in your own code! Try replacing HOC with a Render Prop component. When you do that, you’ll no longer be stuck with the red tape of HOC, and you’ll also take advantage of the dynamic composition model that React gives you, which is a particularly cool feature. 😎

Michael is a member of React Training and a prolific open source software contributor to the React community. To get the latest on Training and classes, subscribe to the mailing list and follow React Training on Twitter.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, React, front-end, back-end, product, design and other fields. If you want to see more high-quality translation, please continue to pay attention to the Project, official Weibo, Zhihu column.