Use Render Props with React

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated a year ago

In this lesson, we'll take a step back and re-examine the problem of sharing Component logic by iterating our way to arrive at a new pattern called render props. A render prop is a function that renders JSX based on state and helper arguments. This pattern is the most flexible way to share component logic while giving complete UI flexibility. It's a remarkably simple and powerful pattern.

Instructor: [00:00] In our usage example here, we want to have complete and entire control over the rendering. We want to be able to render a div. We want to be able to render a switch and a custom button. Right now, with the way that our toggle is implemented, it only renders a switch and it has complete control over rendering.

[00:16] We're going to iterate to support this API. We'll start by taking this and creating a new function called renderUI. Then we'll simply replace render with return this.renderUI. Next, we'll turn renderUI into a pure function, so it accepts on and toggle in an object.

[00:36] Instead of referencing this.toggle, it just passes toggle. Then we pass those things to a renderUI function on it's this.state.on and toggle is this.toggle. Everything's still working as it was before. Now, because this is a pure function, it doesn't actually need to exist on the instance.

[00:53] It's not using any instance method or properties so we can pull this off, make it an error function and then remove this dot so that it references the error function we have to find up here. That's still working.

[01:08] Next, let's go ahead and make a static default props here, and we'll accept a prop called renderUI, and we'll make the default of renderUI be this function. Then we can return this.props.renderUI, and now users of our component could use that API. S

[01:24] We'll pull this out, we'll say renderUI equals at error function and we get exactly what we were looking for. Now we have total and complete control over rendering of this component. We get all the information that we need, the state and any event handlers to change that state, and then we can return the JSX that we want to have rendered for this component.

[01:47] Now, our use case doesn't really make sense to provide a default implementation for renderUI, so we'll get rid of that default prop, and also it's more common to call this renderUI prop children. That all still works, and because it's called children, we can actually have the API we were looking for in the first place. Everything works great.

[02:08] We iterated to this solution, but it's actually quite simple. To support a render prop API, you simply remove all the contents of the render method and return this.props.children and call that as a function. You provide any state and state updaters or helper functions that your consumers need so they can be responsible for rendering.

[02:29] This gives people ultimate flexibility over how your component is rendered, and this is the render prop API. It's the most primitive form of UI flexibility, and any other pattern can be implemented on top of this API.

[02:41] One of the really nice things about this API is you can actually implement the previous API that we had with the new API. If you have a common use case, we can say function common toggle. That'll accept some props. Then you can return toggle, spread those props, and then you can provide your own children function that renders the common UI.

[03:03] In our case, we could provide switch on is on and on click is toggle. And then we'll de-structure the state and the helpers. And now people can use the common toggle which is limited in flexibility but has a simpler API, and it's all built on top of the render prop API that toggle exposes.

Sudaman Shrestha
Sudaman Shrestha
~ 4 years ago
Jay
Jay
~ 3 years ago

Had to watch a couple of times.... Really great, finally seeing how this can be used effectively. Thanks!

Phillip Chan
Phillip Chan
~ 3 years ago

Sorry, I don't understand how this makes the rendering more flexible? Before we refactored to leverage render props, it seemed like we were still able to "control the rendering" of the child component because we are rendering the children:

<Toggle onToggle={onToggle}>
			{({on, toggle}) => (
				<div>
					{on ? 'The button is on' : 'The button is off'}
					<Switch on={on} onClick={toggle} />
					<hr />
					<button
						aria-label="custom-button"
						onClick={toggle}
					>
						{on ? 'on' : 'off'}
					</button>
				</div>
			)}
		</Toggle>

Can someone explain why this is more "flexible"?

 <Toggle 
    onToggle={onToggle}>
    renderUI={({on, toggle}) => (
        <div>
          {on ? 'The button is on' : 'The button is off'}
          <Switch on={on} onClick={toggle} />
          <hr />
          <button aria-label="custom-button" onClick={toggle}>
            {on ? 'on' : 'off'}
          </button>
        </div>
      )}
    />
Phillip Chan
Phillip Chan
~ 3 years ago

Sorry, I don't understand how this makes the rendering more flexible? Before we refactored to leverage render props, it seemed like we were still able to "control the rendering" of the child component because we are rendering the children:

<Toggle onToggle={onToggle}>
			{({on, toggle}) => (
				<div>
					{on ? 'The button is on' : 'The button is off'}
					<Switch on={on} onClick={toggle} />
					<hr />
					<button
						aria-label="custom-button"
						onClick={toggle}
					>
						{on ? 'on' : 'off'}
					</button>
				</div>
			)}
		</Toggle>

Can someone explain why this is more "flexible"?

 <Toggle 
    onToggle={onToggle}>
    renderUI={({on, toggle}) => (
        <div>
          {on ? 'The button is on' : 'The button is off'}
          <Switch on={on} onClick={toggle} />
          <hr />
          <button aria-label="custom-button" onClick={toggle}>
            {on ? 'on' : 'off'}
          </button>
        </div>
      )}
    />
Phillip Chan
Phillip Chan
~ 3 years ago

Sorry, I don't understand how this makes the rendering more flexible? Before we refactored to leverage render props, it seemed like we were still able to "control the rendering" of the child component because we are rendering the children:

<Toggle onToggle={onToggle}>
			{({on, toggle}) => (
				<div>
					{on ? 'The button is on' : 'The button is off'}
					<Switch on={on} onClick={toggle} />
					<hr />
					<button
						aria-label="custom-button"
						onClick={toggle}
					>
						{on ? 'on' : 'off'}
					</button>
				</div>
			)}
		</Toggle>

Can someone explain why this is more "flexible"?

 <Toggle 
    onToggle={onToggle}>
    renderUI={({on, toggle}) => (
        <div>
          {on ? 'The button is on' : 'The button is off'}
          <Switch on={on} onClick={toggle} />
          <hr />
          <button aria-label="custom-button" onClick={toggle}>
            {on ? 'on' : 'off'}
          </button>
        </div>
      )}
    />
Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

Phillip, I think that you're confused. If you look at the initial implementation the Toggle component doesn't call the children function (or a renderUI function) so what's rendered is determined by the Toggle component (it only renders a switch). Try watching the video again?

Phillip Chan
Phillip Chan
~ 3 years ago

Hi Kent! Thanks for responding. You are correct. Thanks for clarifying!

jamesperi
jamesperi
~ 3 years ago

Interestingly I ran into an error when I clicked on the toggle after using the CommonToggle. This was the result of not passing it the onToggle prop. Since Toggle components setState method assumes the onToggle prop, it fails. Solved by creating a deafultProp to the CommonToggle function or testing for an onToggle prop before calling it in the setState.

Kai Lovingfoss
Kai Lovingfoss
~ 3 years ago

I really like these videos. I wish I didn't feel like a dum dum each time I watch these videos, but I appreciate it all the same. Thank you Kent!

Erick
Erick
~ 2 years ago

Is there a reason the children function accepts an object instead of two arguments. Seems would be simpler. Thanks!