Day 4 - Render props

May 17, 2019 · 100 days of code, React, render props · 5 minute read

Render what?

Render props is one of the React patterns that was on my "React To Do list" for some time. So today I decided to check out what the pattern is and to experiment with it.

As soon as I started reading what the pattern was it became clear to me it's not a thing I will totally master within one day. My goal today is to have a basic understanding which problem it solves. And the best way to do that is to make a very simple component and bring render props into practice.

So what is render props?

Render props is a pattern you can use to separate how a component works from how it looks.

If you make this separation you can reuse the component's functionality in other components that each define their own UI for that functionality. It also can be a nice way to just separate the functionality from the UI when either one is complicated and you want your code to be more readable or easier testable.

Toggle component

Let's create a very simple component, a toggle component. Its functionality is that you can turn something on or off. And the component renders a button to allow that:

function App() {
  return (
    <div>
      <Toggle />
    </div>
  );
}

function Toggle() {
  const [on, setOn] = useState(false);

  return <button onClick={() => setOn(!on)}>{on ? "ON" : "OFF"}</button>;
}

Let's break this down.

What happens here is that the App component renders a Toggle component. In the Toggle component we render a button that you can press to turn it on or off. The button's text contains the current state of the component: "ON" when the on variable is true, otherwise "OFF".

What the component renders depends on the value of the on variable. That's why we call React's useState hook. The useState hook returns two things: the on variable that contains the current boolean value indicating the state is on or off and the setOn function which we must use to change the value of the on variable.

With the button's onClick we call the setOn function, which makes React re-render the component which in turn results in the button showing the current state.

What we have now is just a Toggle component, we haven't done anything with render props.

Let's refactor first

The toggle button is fine, but wouldn't it be nice to optionally replace it with a checkbox? To make this possible the first step to take is moving the button out of the Toggle component into a function:

function renderToggleButton(toggle, on) {
  return <button onClick={toggle}>{on ? "ON" : "OFF"}</button>;
}

This function has two arguments: a toggle function and the current on boolean value. The function renders a button with an onClick that calls the toggle function. Note we do not pass anything to the toggle function. It's not this function's responsibility to do the actual toggling. It only defines the UI and when toggling should take place.

Now we update the Toggle component. It no longer renders the button, but it will call the renderToggleButton function we just created. It passes a function that calls setOn and we also pass the current on boolean value:

function Toggle(props) {
  const [on, setOn] = useState(false);

  return <>{renderToggleButton(() => setOn(!on), on)}</>;
}

The only thing we did here was refactor the code by separating the toggle functionality from the toggle UI. It still only renders the toggle button.

Let's create an alternative UI for toggling, a checkbox. This function returns a checkbox and also displays "ON" of "OFF" respectively:

function renderToggleCheckbox(toggle, on) {
  return (
    <>
      <input type="checkbox" checked={on} onChange={toggle} />
      {on ? "ON" : "OFF"}
    </>
  );
}

Here we also get the toggle function passed in and call it when the checkbox is checked or unchecked. But the UI is totally different.

Now we can change the Toggle component so it renders a checkbox instead of a button:

function Toggle() {
  const [on, setOn] = useState(false);

  return <>{renderToggleCheckbox(() => setOn(!on), on)}</>;
}

The toggle functionality still works, but looks different. We have verified our Toggle component has become independent from the UI.

Render props at last

The final step we have to take is to tell the Toggle component which UI it has to render. And we'll do that by passing in that UI. And as we already created two functions that return the toggle UI, we need to change the Toggle component so it can receive one of these functions. The way you pass in a function (or any value for that matter) to a component is by using props. And what we will pass via props is a render function hence the name render props.

Let's render the Toggle component twice so we can pass in both render functions:

function App() {
  return (
    <div>
      <Toggle render={renderToggleButton} />
      <Toggle render={renderToggleCheckbox} />
    </div>
  );
}

Note that the name of the prop containing the render function is called render. However, this is not required, it's just a naming convention, you can give it any name you like.

The Toggle component should now receive props and call the render function from props:

function Toggle(props) {
  const [on, setOn] = useState(false);

  return <>{props.render(() => setOn(!on), on)}</>;
}

And that's it! We created a Toggle component that is responsible for toggle functionality. How the UI looks is not its concern. It gets passed in a render prop containing a function it calls to render a UI. What we can do now is create lots of different UIs that implement ways to toggle.

I am really excited I have not only learned the basics of the render props pattern but also implemented my first render props and created this blog to explain it to myself and you. My hands are itching (Dutch for being very eager) to create the most ridiculous toggle UIs I can think off, but maybe that's something for a next time.

Check my render props implementation on CodeSandbox or the Github repo for the full code of this blog post. Feel free to experiment with it.

And if you want to know more about render props, these two blog posts are great:

I ignored that last blog post when writing this blog post, but I think it's food for thought and maybe even reason for a second render props blog post.

Share on Twitter · Discuss on Twitter · Edit on GitHub