Skip to content

Add doc about Render Props #355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 12, 2018
Merged

Conversation

mjackson
Copy link
Member

This PR is a re-submission of a PR that I had previously made to facebook/react but that was closed when the website moved to reactjs/reactjs.org. Sorry it took so long 😅

I'm planning on adding one more section that discusses the trade-offs when using a render prop with React.PureComponent, which was the source of some concern in the earlier submission.

/cc @thejameskyle who submitted #349 earlier today which discusses the same concept

@reactjs-bot
Copy link

reactjs-bot commented Nov 27, 2017

Deploy preview for reactjs ready!

Built with commit dcd53fd

https://deploy-preview-355--reactjs.netlify.com

@toddsby
Copy link

toddsby commented Dec 1, 2017

I was under the impression this was an anti-pattern due to performance considerations. Thoughts @acdlite @gaearon @vjeux I know Dan has weighed in briefly on twitter but I couldn't find a write up on this.

@mjackson
Copy link
Member Author

mjackson commented Dec 1, 2017

@toddsby The main concern with using a render prop is when you use them with pure components, as I've already outlined in the "Caveats" section. If you think there's something else I can add there, I'm all ears.

@alexkrolick alexkrolick requested review from acdlite and removed request for acdlite December 1, 2017 18:40
permalink: docs/render-props.html
---

The term ["render prop"](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce) refers to a simple technique for sharing code between React components using a prop whose value is a function.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to kick off this page with a simple example and motivation to frame the conversation. The HOC doc starts with:

Concretely, a higher-order component is a function that takes a component and returns a new component.
const EnhancedComponent = higherOrderComponent(WrappedComponent);
...explain at high-level where and when HOCs are useful...

Paraphrasing the example code from @thejameskyle's #349, how about this:


The term "render prop" refers to a simple technique for sharing code between React components using a prop whose value is a function.

A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

By inverting control of the render method to the consumer of the component, render props provide a flexible way to encapsulate and share logic between components.

Libraries that use render props include React Router and the enhanced input library Downshift.

In this document, we’ll discuss why render props are useful, and how to write your own.

}
```

In cases where you cannot bind the instance method ahead of time in the constructor (e.g. because you need to close over the component's props and/or state) you should extend `React.Component` instead.
Copy link
Collaborator

@alexkrolick alexkrolick Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about mentioning some other ways of writing render props at the end?


Using Props Other Than render

Although the examples above use render, any prop can be used to implement the render prop pattern. For example, the component below passes a function as the children prop:

<WithTitleAsync url="/my/api/books/123">
  { ({ title }) => <h1>{title}</h1> }
</WithTitleAsync>
class WithTitleAsync extends React.Component {
  constructor(props) {
    super(props);
    this.state = { title: null };
  }

  componentDidMount() {
    fetch(this.props.url)
      .then(
        ({ title }) => { this.setState({ title }) },
        (error) => { this.setState({ error }) }
      )
  }

  render () {
    if (this.state.error) {
      return <span>Title Not Found</span>;
    } else if (!this.state.title) {
      return <span>Getting Title...</span>
    } else {
      return this.props.children(this.state.title);
    }
  }
};

WithTitleAsync.propTypes = {
  children: PropTypes.func.isRequired,
};

}
}
```

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving this to a separate comment

Here's an idea for an optimization section. I don't think it's entirely necessary for the first pass and could certainly be added later.


Optimizing Render Props

If you've benchmarked your app and discovered that a render prop declaration is a performance bottleneck, here are some techniques to optimize:

  • Move inline functions to be declared outside the render.

Before: Inline Render Prop

class ABC extends React.Component {
  render() {
    <MyComponent render={() => <h1>Hello World</h1>}
  }
}

After: Declared Outside Class

class ABC extends React.Component {
  render() {
    return <MyComponent render={helloWorld} />
  }
}

function helloWorld() {
  return <h1>Hello World</h1>
}

After: Using Getters

class ABC extends React.Component {
  get helloWorld() {
    return <h1>Hello World</h1>
  }

  render() {
    return <MyComponent render={this.helloWorld} />
  }
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memoizing will solve the PureComponent caveat mentioned in the doc wouldn't it?

Copy link

@Jessidhia Jessidhia Dec 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit: your "Using Getters" example passes a react element as the render prop, not a function)

@timwis
Copy link

timwis commented Dec 5, 2017

Thanks for this write-up! This pattern makes things simpler :)

@alexkrolick
Copy link
Collaborator

Is anyone intending on adding a review for this? If not I propose merging and iterating via PR.

cc @mjackson @thejameskyle @gaearon

@mjackson
Copy link
Member Author

mjackson commented Dec 9, 2017

I'd like to add a few of the edits you proposed, @alexkrolick, and then I'll go ahead and merge. Thanks for the review 😅


// This binding ensures that `this.renderTheCat` always refers
// to the *same* function when we use it in render.
this.renderTheCat = this.renderTheCat.bind(this);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we often see the usage of arrow functions instead of binding in the constructor.
Is there any particular reason not to use an arrow function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The whole point of this example is that we create the function only once instead of dynamically creating a function on every render. Unless you're suggesting I use an arrow function here in the constructor?

Copy link

@ron23 ron23 Dec 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I meant Instead of this:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);

    // This binding ensures that `this.renderTheCat` always refers
    // to the *same* function when we use it in render.
    this.renderTheCat = this.renderTheCat.bind(this);
  }

  renderTheCat(mouse) {
    return <Cat mouse={mouse} />
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

Maybe this makes more sense/more consistent?

class MouseTracker extends React.Component {
   // `this.renderTheCat` always refers to the *same* function when we use it in render.
  renderTheCat = (mouse) => {
    return <Cat mouse={mouse} />
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

Less lines, less confusing for newcomers, same effect (creating a single function instance)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs intentionally try to stick to standard syntax instead of proposal-stage. Class properties are getting there but aren't standard just yet 😄

https://github.com/reactjs/reactjs.org/blob/master/CONTRIBUTING.md#dont-use-features-that-arent-standardized-yet

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexkrolick my bad. I wasn't aware it isn't standard yet (with babel, the difference between standard and non standard is blurry). Thanks for the clarification.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW @ron23 I always write my code in the way you're suggesting here and never actually write out my constructors, so I feel you. But ya, the React docs tend to favor .bind at present.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// This binding ensures that this.renderTheCat always refers
// to the same function when we use it in render.

Is there any case that this.renderTheCat refers a different function in render without this?
I know that this in the function may not be a MouseTracker's instance, but I guess the reference to the function is always the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koba04 I believe you're right and PRed a fix: #700. The example accidentally conflates extracting the function as a method and binding that method.

@mjackson mjackson merged commit 6efa02d into reactjs:master Jan 12, 2018
jhonmike pushed a commit to jhonmike/reactjs.org that referenced this pull request Jul 1, 2020
* started translating 2016-04-07-react-v15

* translated introduction and installation guide sections

* started to translate the changelog

* finish the translation of React v15.0

* adhere to the official translation glossary

* Remove extra "e" in content/blog/2016-04-07-react-v15.md

Co-Authored-By: Jussara Soares <jussara.ac.s@hotmail.com>

* Update content/blog/2016-04-07-react-v15.md to replace "mudanca" with "mudança"

Co-Authored-By: Jussara Soares <jussara.ac.s@hotmail.com>

* Update content/blog/2016-04-07-react-v15.md

Co-Authored-By: Jussara Soares <jussara.ac.s@hotmail.com>
BetterZxx pushed a commit to BetterZxx/react.dev that referenced this pull request Mar 21, 2023
)

原文`End-to-end tests are useful for testing longer workflows, ...`,此处应为`有用`而非`长有用`。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants