In this lesson, we'll take a step back and reexamine the problem by doing a few tiny refactorings to arrive at a new pattern called render props
. render props
is just a function that you pass to a component that renders jsx. This pattern gives us the ability to use Reacts lifecycle methods where as with Higher Order Components, you could not. We'll see that it allows us to avoid using APIs like context
and all of the issues we had to deal with when using Higher Order Components.
Instructor: [00:00] Let's go ahead and refactor this so that users of the toggle component can have a little bit more control over what's rendered and how it's rendered.
[00:06] The first thing that we're going to do is I'm going to take what's rendered in the component itself, and I'm going to move it to a new function called renderSwitch. I'm going to take all of this and move it up to renderSwitch.
[00:20] In my render, I'll return this.renderSwitch. Cool, nothing has changed. It all works fine.
[00:27] Let's make this a pure function. It accepts some arguments, and it returns the same thing given the same arguments. Instead of referencing this.state and this.toggle, I'm going to accept on and toggle as arguments. I'll get rid of this, and I'll get rid of this.
[00:46] When I call renderSwitch, I'm going to give it an object with on, this.state.on, and toggle is this.toggle. Cool, everything else is working. What's neat about this is now that this is totally stateless, it has no need to be on the class toggle component at all.
[01:06] We could actually move this out entirely, call this function renderSwitch, and then no longer need to call it on this. Everything still works exactly as it was before.
[01:17] What if instead of using the renderSwitch function that's available on the scope of this toggle class, I accept that as a prop, this.props.renderSwitch? Then the app component could simply say renderSwitch is going to be renderSwitch.
[01:35] Everything is still working. It's working exactly as it was before. Well, that's kind of neat.
[01:46] I have quite a bit of control. What if I inline an arrow function here or something? Then I can accept the arguments on and toggle. Now I can say, "Div and switchOn is on, and onClick is toggle."
[02:07] Then I can say, "If it's on, then on. Otherwise, off." Cool, now I click, and it works.
[02:18] What's neat about this is I don't have to worry about the depth of my tree at all. It all works fine just out of the box because this is simply a function that's returning JSX and accepting the state and some functions. Now I can get rid of this renderSwitch.
[02:33] Maybe to make this a little bit more generic, we could call this render. All of a sudden, people have total and complete control. We're able to do all of this without using any weird APIs, like context or anything. It's all happening right within this toggle component.
[02:49] It's as simple as returning a function called to the render function that we're provided. That render function is provided the state and any helpers to make changes to that state. It's free to render whatever it wants or nothing if it doesn't want to render anything at all. This pattern is called render props, and it's super awesome.
[03:09] Before we move on, I want to compare the app with the render props solution with the app and the higher-order component and compound component solution. The first thing that bothers me with higher-order components is that I have to wrap everything.
[03:24] If I have a my-toggle that needs to get access to the state and the toggle helper, then I need to wrap that and create a new component, and that's the one that I render. Just this alone presents a whole lot of problems that we had to solve in our higher-order component with the display name, the wrapped component, the React statics, the ref.
[03:44] On top of that, we have this naming collision. If I wanted to use a toggle prop for any of the components that are using this higher-order component, that's a problem. In addition, if I were to use a withOther toggle with this component, I'm going to have a name collision there as well. There's no way for React to warn me that that is going to be a problem.
[04:07] The other thing that's annoying is what if I have another prop here that says, "Say Hi"? I look at the my-toggle component, I see that it's wrapped, and I wonder to myself, "OK. So which one of these props is coming from this withToggle higher-order component?" I have to look at the implementation to understand.
[04:26] I can't even look at where it's being rendered. I have to look at the implementation of the higher-order component to know which of these props are being passed from where. The problem gets aggravated if I compose higher-order components together.
[04:37] Another thing that's a little bit of a problem with higher-order components, that we're not going to go into too far, is typing with TypeScript or Flow. It's a lot harder to type a higher-order component than it is to type a render prop.
[04:49] Finally and probably the biggest point is where the composition takes place -- what composition mechanism do we have available to us with these two approaches? With higher-order components, the composition takes place statically during the construction phase of our application. We construct these components, and we compose behaviors with these components, and then we can render those things in our application.
[05:11] However, with the render prop approach, the composition happens right within React's normal composition model, during the render phase. This means that I can take advantage of React's lifecycle and the natural flow of props and state because the composition model is dynamic.
Just normal props. That was part of what I hoped would come out of this video. There's nothing special about render props (other than the fact they are a pattern that unlocks a LOT of power) :)
How is this different from using just children?
Vinuth,
You mean how is it different from doing return this.props.children
? Well, the render prop is a function that gets invoked, so it has access to state and helpers that it can use when rendering. If you meant: "how is this different from return this.props.children({ /* stuff */ })
then it's basically the same, just a different prop with a more sensible name.
What would be good use cases to use this pattern?
There are many use cases for this. Genetically, it's a useful pattern to separate logic implemention from UI rendering. For some examples of Components using this pattern see here: https://github.com/jaredpalmer/awesome-react-render-props
Hi Kent, this is a great pattern! Thanks for another great video series and as always your concise explanations! I am researching and recommending component patterns for a large React component base, and I feel like render props are the better choice for testability and maintainability versus higher order components or compound components. I don't see the cost being too great for even simpler components. Do you have any scenarios where you would recommend against them?
Hey Shan! I'm glad you like the pattern :)
I would suggest that render props make a great base component, then you can build components on top of that which implement other patterns. The reason you'd want to do that is other patterns can be more ergonomic for basic use cases, but they limit the flexibility. So for folks who don't need the flexibility they can use the more ergonomic components, and for those who need the flexibility they can use the base component that uses a render prop. I'm planning on writing about this on my newsletter next week probably: http://kcd.im/news
Thanks!
Hello Kent,
First of all thank you for an amazing Video Series. It has helped me a lot in understanding how to use React and its functionalities.
You mentioned in the intro that "This pattern gives us the ability to use Reacts lifecycle methods where as with Higher Order Components, you could not."
Why is this so? I am unable to understand why lifecycle methods can't be used on components which are wrapped with HOC.
Also, you mentioned that we have to see the implementation of the HOC to see which props are passed down by the HOC. Isn't this a problem in the Render props pattern as well? There too we have to go ahead and look at the implementation of the logical component which is calling our render-prop function to know which props will be passed to our function.
Also, should render-prop always accept functions only or it can accept a component class as well?
render props is special or just normal props?