What are Render Props?

The new context API uses render props:

<ThemeContext.Consumer>
  {theme => (
    <button
      {. props}
      style={{backgroundColor: theme.background}} / >
  )}
</ThemeContext.Consumer>
Copy the code

Most people are surprised when they first see this syntax, because in everyday code, props. Children must be strings or elements. But in fact, props. Children can be functions, as long as the resulting render returns a DOM element. Such as:

// chilren props
const Test = props= > props.children('hello world')
const App = (a)= > (
    <Test>
        {text => <div>{text}</div>}
    </Test>
)

ReactDOM.render((<App />, root) // Returns<div>hello world</div>
Copy the code

This is a render props, although it has no practical meaning. Instead of defining the render function itself, the component passes in an externally defined render function called render props. In the example above, it would look like this:

// render props
const Test = props= > props.render('hello world')
const App = (a)= > (
    <Test
      render={text= > <div>{text}</div>}
    />
)

ReactDOM.render((<App />, root) // Returns<div>hello world</div>
Copy the code

Because the render function is so large in the real world, for code cleanliness it is more likely to use children than custom Render to receive the external Render function. So this technique could also be called children props (a more obscure term than render props), but generally referred to as render props.

Why use such weird syntax?

For reusability. React is componentized to facilitate reuse. In most scenarios we need to reuse the UI (such as article lists, sidebars), but in a few cases we need to reuse functionality and state (such as context).

If the core of React isState => UIThe common component is UI reuse, thenrender propsThis was created for State reuse.

Render Props miniature schnauzer

An interesting history of render props before the Demo begins.

  • First noticed was the React Motion animation library written by Cheng Lou of Facebook.
import { Motion, spring } from 'react-motion';

<Motion defaultStyle={{x: 0}} style={{x: spring(10)}} >
  {value => <div>{value.x}</div>}
</Motion>
Copy the code
  • This has since become widely accepted. Kent C. Dodds, who has written some of the best front-end tutorials, is a fan of render props, and his PayPal input box component, Downshift, also uses Render props

  • Michael Jackson, author of the React-Router, is also a strong advocate of render props. He tweeted a controversial line:

Next time you think you need a HOC (higher-order component) in @reactjs, you probably don’t.

Translation: The next time you want to use HOC to solve a problem, you probably don’t. In his reply he added,

I can do anything you’re doing with your HOC using a regular component with a render prop. Come fight me.

That is, anything HOC can do, Render props can do. It is worth noting that the only HOC in React-Router 4 is withRouter, which is implemented using render props. If you are interested, check out the source code.

  • HOC is good, but writing a “really good” HOC requires a lot of work (the New React API)fowardRefIs almost born for this), is a comfortable writing vexed existence. So there seems to be a trend of “less HOC and more render props” these days. The new Context API though is the same ideareact-reduxThe same, but the choice of render props, it seems to me is also reasonable.

Render Props use scenarios

Example 1: An everyday use scenario is a popover. The App’s popover UI may be strange, but their function is similar: there is only a way to show the hidden state, and a way to control the hidden state. Take ANTD for example:

import { Modal, Button } from 'antd';

class App extends React.Component {
  state = { visible: false }
  showModal = (a)= > {
    this.setState({
      visible: true}); } handleOk =(e) = > {
    // Do something about it
    this.setState({
      visible: false}); } handleCancel =(e) = > {
    this.setState({
      visible: false}); } render() {return (
      <div>
        <Button onClick={this.showModal}>Open</Button>
        <Modal
          title="Basic Modal"
          visible={this.state.visible}
          onOk={this.handleOk}
          onCancel={this.handleCancel}
        >
          <p>Some contents...</p>
          <p>Some contents...</p>
          <p>Some contents...</p>
        </Modal>
      </div>); }}Copy the code

This is the simplest example of Modal use, but the ideal way to use Modal is as follows:

  <div>
    <Button>Open</Button>
    <Modal
      title="Basic Modal"
      onOk={this.handleOk// do something} >
      <p>Some contents...</p>
      <p>Some contents...</p>
      <p>Some contents...</p>
    </Modal>
  </div>
Copy the code

I just want to write the onOK of the business logic, the other parts are the implementation details of the popover, why can’t they be encapsulated?

The answer is yes. Let’s write a Pop component using render props to encapsulate all the logic. The desired end use is:

<Pop>
    {({ Button, Modal }) => (
      <div>
        <Button>Open</Button>
        <Modal 
          title="Simple" 
          onOK={()= > alert("everything is OK")}
        >
          <p>Some contents...</p>
          <p>Some contents...</p>
          <p>Some contents...</p>
        </Modal>
      </div>
    )}
  </Pop>
Copy the code

You can try to write it yourself. I wrote as follows:

import { Modal, Button } from 'antd';

class Pop extends React.Component {
  state = { on: false };
  toggle = (a)= > this.setState({ on:!this.state.on });
  // Wrap the ANTD component around state and method
  MyButton = props= ><Button {... props} onClick={this.toggle} />;
  MyModal = ({ onOK, ... rest }) = > (
    <Modal
      {. rest}
      visible={this.state.on}
      onOk={()= >{ onOK && onOK(); this.toggle(); }} onCancel={this.toggle} /> ); render() { return this.props.children({ on: this.state.on, toggle: this.toggle, Button: this.MyButton, Modal: this.MyModal }); }}Copy the code

Complete the Demo

To put it simply, render props delegate how to render components to the components that use it, but at the same time provide states and methods to the outside world in the form of parameters that need to be reused. Implement UI customization and function reuse. This example is a bit radical, however, providing not only states and methods, but also stateful components as parameters. If you have different opinions, please leave a message and learn from each other.

Example 2: Generic render props encapsulates only “state”. Dan Abromov gives a good example of this in the React documentation: mouse tracking. This feature has many applications and is easy to implement:

class Mouse extends React.Component {
  state = { x: 0.y: 0 }

  handleMouseMove = e= > 
    this.setState({ x: e.clientX, y: e.clientY })
  
  render() {
    return (
      <div style={{ height: '100vh'}}onMouseMove={this.handleMouseMove}>
        <p>Mouse over ({this.state.x}, {this.state.y})</p>
      </div>)}}Copy the code

But how do you encapsulate and reuse this functionality? Like a picture of a cat following a mouse. You can try it first. Here are the answers:

/ / packaging
class Mouse extends React.Component {
  state = { x: 0.y: 0 }

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

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {this.props.children(this.state)}
      </div>)}}/ / reuse
const Cat = (a)= > 
  <Mouse>
    {({x,y}) => 
      <img src="/cat.jpg" 
        style={{ position: 'absolute', left: x, top: y }} 
      />
    }
  <Mouse>
Copy the code

conclusion

If it’s too long to read, there’s only one I want to share: When you’re writing a project and you need to reuse functions rather than THE UI, try encapsulating a component with render props. HOC is also a solution, of course, but a discussion of HOC vs Render props will be left for another article.