DEV Community

Cover image for React Design Pattern /Render Props Pattern
Ogasawara Kakeru
Ogasawara Kakeru

Posted on

React Design Pattern /Render Props Pattern

Render Props Pattern is a way of making components very reusable.
The component simply renders a props, not JSX element.

In case we have a Title component. In this case, the title component shouldn't do anything except for rendering the value that we pass.
We can use a render prop for this.

<Title render={() => <h1>I am a render prop!</h1>} />
Enter fullscreen mode Exit fullscreen mode

The above snippet shows that the Title component renders an h1 element as a render prop.

const Title = (props) => props.render();
Enter fullscreen mode Exit fullscreen mode

To the Component element, we have to pass a prop called render, which is a function that returns a React element.

import React from "react";
import { render } from "react-dom";

import "./styles.css";

const Title = (props) => props.render();

render(
  <div className="App">
    <Title
      render={() => (
        <h1>
          <span role="img" aria-label="emoji">
            ✨
          </span>
          I am a render prop!{" "}
          <span role="img" aria-label="emoji">
            ✨
          </span>
        </h1>
      )}
    />
  </div>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

The cool thing about render props is that the component that receives the prop is very reusable.

We can use it multiple times, passing different values to the render prop each time.

import React from "react";
import { render } from "react-dom";
import "./styles.css";

const Title = (props) => props.render();

render(
  <div className="App">
    <Title render={() => <h1>✨ First render prop! ✨</h1>} />
    <Title render={() => <h2>🔥 Second render prop! 🔥</h2>} />
    <Title render={() => <h3>🚀 Third render prop! 🚀</h3>} />
  </div>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Although each of the render props is called, any prop that renders JSX is considered a render prop. We should rename the render props.

index.js
import React from "react";
import { render } from "react-dom";
import "./styles.css";

const Title = (props) => (
  <>
    {props.renderFirstComponent()}
    {props.renderSecondComponent()}
    {props.renderThirdComponent()}
  </>
);

render(
  <div className="App">
    <Title
      renderFirstComponent={() => <h1>✨ First render prop! ✨</h1>}
      renderSecondComponent={() => <h2>🔥 Second render prop! 🔥</h2>}
      renderThirdComponent={() => <h3>🚀 Third render prop! 🚀</h3>}
    />
  </div>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Thanks to fixing, we can use render props in order to make a component reusable, as we can pass different data to the render prop each time.

Why would you want to use this?

A component that takes a render prop usually does a lot more than simply invoking the render prop. Instead, we usually want to pass data from the component that takes the render prop, t the element that we pass as a prop.

function Component(props) {
  const data = { ... }

  return props.render(data)
}

Enter fullscreen mode Exit fullscreen mode

The render prop can receive this value that we passed as its argument.

<Component render={data => <ChildComponent data={data} />}
Enter fullscreen mode Exit fullscreen mode

We have a simple app, where a user can type a temperature in Celsius. The app shows the value of this temperature in Fahrenheit and Kelvin.

import React, { useState } from "react";
import "./styles.css";

function Input() {
  const [value, setValue] = useState("");

  return (
    <input
      type="text"
      value={value}
      onChange={e => setValue(e.target.value)}
      placeholder="Temp in °C"
    />
  );
}

export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input />
      <Kelvin />
      <Fahrenheit />
    </div>
  );
}

function Kelvin({ value = 0 }) {
  return <div className="temp">{value + 273.15}K</div>;
}

function Fahrenheit({ value = 0 }) {
  return <div className="temp">{(value * 9) / 5 + 32}°F</div>;
}

Enter fullscreen mode Exit fullscreen mode

There's a problem. The stateful Input component contains the value of the user's input, meaning that the Fahrenheit and Kelvin component don’t have access to the user’s input.

Lifting state

One way to make the users input available to both the Fahrenheit and Kelvin component in the above example, we'd have to lift the state.

In this case, we have a stateful Input component. However, the sibling components Fahrenheit and Kelvin also need access to this data. Instead of having a stateful Input component, we can lift the state up to the first common ancestor component that has a connection to Input, Fahrenheit and Kelvin: the App component in this case!

function Input({ value, handleChange }) {
  return <input value={value} onChange={(e) => handleChange(e.target.value)} />;
}

export default function App() {
  const [value, setValue] = useState("");

  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input value={value} handleChange={setValue} />
      <Kelvin value={value} />
      <Fahrenheit value={value} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Although this is a valid solution, it can be tricky to lift state in larger applications with components that handle many children. Each state change could cause a re-render of all the children, even the ones that don’t handle the data, which could negatively affect the performance of your app.

Render props
Instead, we can use props.
Here is a changed Input component in a way that it can receive render props.

function Input(props) {
  const [value, setValue] = useState("");

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Temp in °C"
      />
      {props.render(value)}
    </>
  );
}

export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input
        render={(value) => (
          <>
            <Kelvin value={value} />
            <Fahrenheit value={value} />
          </>
        )}
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Children as a function

Additionally, we can pass functions as children to React components.
This function is available to us through the children prop, which is technically also a render prop.

Let's change the Input component. Instead of explicitly passing the render prop, we'll just pass a function as a child for the Input component.

export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input>
        {(value) => (
          <>
            <Kelvin value={value} />
            <Fahrenheit value={value} />
          </>
        )}
      </Input>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We can access to this function, through the props.children props that's available on the Input component. In sted ob calling props.render with the value of the user input, we'll props.children with the value of the user input.

  const [value, setValue] = useState("");

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Temp in °C"
      />
      {props.children(value)}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
import React, { useState } from "react";
import "./styles.css";

function Input(props) {
  const [value, setValue] = useState(0);

  return (
    <>
      <input
        type="number"
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Temp in °C"
      />
      {props.children(value)}
    </>
  );
}

export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input>
        {value => (
          <>
            <Kelvin value={value} />
            <Fahrenheit value={value} />
          </>
        )}
      </Input>
    </div>
  );
}

function Kelvin({ value }) {
  return <div className="temp">{parseInt(value || 0) + 273.15}K</div>;
}

function Fahrenheit({ value }) {
  return <div className="temp">{(parseInt(value || 0) * 9) / 5 + 32}°F</div>;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
dotallio profile image
Dotallio

Love how step by step you guide through Render Props all the way to the 'children as function' pattern - made it much clearer for me. Do you ever mix render props with context or do you stick to one or the other?

Collapse
 
kkr0423 profile image
Ogasawara Kakeru • Edited

Thanks for your comment!

To be honest, I'm still learning, and I wrote this post mainly as a way to organize my own understanding.

So I don’t have deep expertise on the topic just yet.

But I hope to gain more practical experience and deepen my knowledge over time.

Really appreciate you taking the time to read it

Collapse
 
brense profile image
Rense Bakker

Render props are legacy and have been replaced by custom hooks legacy.reactjs.org/docs/render-pro...

Collapse
 
kkr0423 profile image
Ogasawara Kakeru

Makes sense. Thanks for the explanation!