React official website Render Props

Render Props

Render Props is a simple technique for prop sharing code between React components using a value as a function.

Components with Render Prop accept 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>) }
/>
Copy the code

Using Render Props to resolve cross-cutting Concerns

Components are the main unit of React code reuse, but it’s not always obvious how to share the encapsulated state or behavior of one component with other components that need the same state.

For example, the following components track the mouse position in a Web application:

class MouseTracker extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() {return (<div style={{height: '100vh'}} onMouseMove={this.handlemousemove}> < / h1 > < p > the current mouse position is ({this. State. X}, {this. State. Y}) < / p > < / div >). }}Copy the code

As the cursor moves across the screen, the component displays its (x, y) coordinates in < p >.

If, at this point, other components also want to get (x, y) coordinates, they need to encapsulate this part of the code logic. Try encapsulating the behavior we want to share in the Mouse component.

class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> {/* ... But how do we render something other than <p>? */} <p>The current mouse position is ({this.state.x}, {this.state.y})</p> </div> ); }} class MouseTracker extends React.Component {render() {return (<> <h1> </h1> <Mouse /> </> ); }}Copy the code

The < Mouse > component now encapsulates all the behavior about listening for Mousemove events and storing Mouse (X, Y) positions, but it’s still not really reusable.

For example, suppose we have a < Cat > component that renders a picture of a Cat chasing a mouse across the screen. We might use < Cat mouse = {{x, y}} /> to tell the component the mouse coordinates to let it know where the image should be on the screen via prop.

First, try rendering the < Cat > component inside the < Mouse > rendering method:

class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class MouseWithCat extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100vh'}} onMouseMove={this.handlemousemove}> {/* We can replace <p> with <Cat>...... here But then we need to create a separate <MouseWithSomethingElse> every time we need to use it, */} <Cat mouse={this.state} /> </div>); }} class MouseTracker extends React.Component {render() {return (<div> <h1> </h1> <MouseWithCat /> </div> ); }}Copy the code

This approach only works for our particular use case, but we have not yet reached the goal of truly encapsulating behavior in a reusable way. Now, every time we want the mouse position to be used for a different use case, we have to create a new component (essentially another < MouseWithCat >) that renders something specifically for that use case.

This is where Render Prop came from: Instead of writing < Cat > directly into a < Mouse > component and effectively changing the render result, we can provide < Mouse > with a function prop to dynamically determine what to render — a Render prop.

class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100vh'}} onMouseMove={this.handleMousemove}> {/* Use render prop to dynamically decide what to render, Instead of giving a static representation of a <Mouse> render result */} {this.props. Render (this.state)} </div>); }} class MouseTracker extends React.Component {render() {return (<div> <h1> </h1> <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); }}Copy the code

We provide a Render method that allows < Mouse > to dynamically determine what needs to be rendered, rather than cloning < Mouse > components and hardcoding them to solve specific use cases.

Specifically, Render Prop is a function prop that tells components what to render.

This technology makes it very easy for us to share behavior. To get this behavior, simply render a < Mouse > component with Render Prop that tells it what the current Mouse coordinates (x, y) are to render.

One interesting thing about Render Prop is that you can implement most of the higher-order components (HOC) using regular components with Render Prop. For example, if you prefer to use withMouse HOC rather than < Mouse > components, you can easily create one using regular < Mouse > with Render Prop:

// If you really want HOC for some reason, then you can easily implement // create one using plain components with Render Prop! function withMouse(Component) { return class extends React.Component { render() { return ( <Mouse render={mouse => ( <Component {... this.props} mouse={mouse} /> )}/> ); }}}Copy the code

Use Props instead of render

It’s important to remember that Render Prop is called Render Prop because of the mode, and you don’t have to use a prop named Render to use this mode. In fact, any function prop that is used to tell components what to render is technically called a “Render prop.”

Although the previous example used Render, we could have simply used Children Prop.

< the rid_device_info_mouse children = {Mouse = > (< p > the Mouse position is {Mouse. X}, {Mouse. Y} < / p >)} / >Copy the code

Children Prop doesn’t really need to be added to the “Attributes” list of JSX elements. Instead, it can be placed directly inside the element.

< Mouse > {Mouse = > (< p > the Mouse position is {Mouse. X}, {Mouse. Y} < / p >)} < / Mouse >Copy the code

Because of the special nature of this technique, when designing a similar API, you might want to declare the children type as a function directly in your propTypes.

Mouse.propTypes = {
    children: PropTypes.func.isRequired
};
Copy the code

Matters needing attention

Be careful when using Render Props with react. PureComponent

If you create functions in the Render method, using Render Prop cancels out the benefits of using React.PureComponent. Because shallow comparisons of props always give false, and in this case each render will generate a new value for render prop.

For example, continuing with the < Mouse > component we used earlier, if Mouse had inherited from react. PureComponent instead of react.ponent, our example would look something like this:

Class Mouse extends React.PureComponent {// Same code as above...... } class MouseTracker extends React.Component { render() { return ( <div> <h1>Move the mouse around! </h1> {/* This is bad! The value of each 'render' prop will be different. */} <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); }}Copy the code

In this case, each time the < MouseTracker > renders, it generates a new function as a prop for < Mouse Render >, thus simultaneously cancelling the effects of the < Mouse > component inherited from the React.PureComponent!

To get around this problem, sometimes you can define a prop as an instance method, something like this:

Class MouseTracker extends React.Component {// Defines an instance method, 'this.renderthecat' always // when we use it in renderings, It refers to the same function renderTheCat(mouse) {return <Cat mouse={mouse} />; } render() { return ( <div> <h1>Move the mouse around! </h1> <Mouse render={this.renderTheCat} /> </div> ); }}Copy the code

If you cannot define a prop statically (for example, because you need to control the exposure of the component props and/or state), then < Mouse > should inherit from react.component.ponent.