In this lesson we look at two React components that render a Chuck Norris joke and share common functionality via a Higher-Order Component. We'll refactor this Higher-Order Component to be a Render Prop component to help make it easier to reason about.
Narrator: [00:00] I have a couple of components here that share functionality by a higher order component. Now on the top right, I have a Chuck Norris joke that I'm pulling from the Chuck Norris API. On the bottom here, I have a more advanced version of that which has the joke, the category that the joke is in, and the URL and this nifty little button you can click and give you a new joke.
[00:22] Let's go ahead and look at our code. The top example is a functional component called joke. Below, I have a similar component called joke advance. It has quite a few properties for us to use.
[00:30] Let's go ahead and look at where I'm invoking these here. As you can see on line 52 and 53, I'm passing my joke and my joke advance component to something called with Chuck joke. Then I'm assigning it to a new variable called nerdy Chuck joke and nerdy Chuck joke advance. Then I am calling that from within my app component here.
[00:49] One of the main reasons why we want to use something like the higher order component or the render prop pattern is for coder use. What they give us is a way to abstract out common logic and state to a place that we can consume with our components without using to duplicate code everywhere.
[01:05] However, one of the downsides of the higher order component pattern is that it's really difficult sometimes to figure out where things are actually coming from. We look back up at our component here. You can see that it is expecting a joke parameter. If we look at where we're actually implementing this component, we're only passing it a category.
[01:24] Where is that joke coming from? This creates some level of indirection and some confusion for the developer because we really have no idea where that's coming from unless we dig and find out it looks like we're passing our joke component to something that may be decorating or embellishing it a little bit. We have to check there.
[01:40] Here we have our with Chuck higher order component which really is a function that we've passed a component to as a parameter. This could be joke or advanced joke or whatever else. It returns a class. This class has state, a fetch joke handler, and on mount, it goes ahead and fetches an initial joke for us.
[01:58] Finally, we return the component that we've passed such as joke or advanced joke. We pass the state and handlers we need in order to make our component reusable. That way we don't have duplicate code in each of our joke components. It's just in one spot. We use it. All that to say is that we get the abstraction and the code reuse that we want using the higher order component.
[02:17] Personally, it's a little hard to reason about. I'm going to go ahead and refactor this higher order component to use a render prop pattern. Let's go ahead and copy our component here and create a new component called Chuck joke.
[02:32] The first thing that we're going to need to do is remove our function since we no longer need that. We'll make our class our default export. We'll name this Chuck joke, save it. I'm going to comment out the return value in our render method because it's going to look quite a bit different.
[02:49] We'll replace it with this.props.children. This is where we'll pass it the common handlers and state that we need in our component, fetch joke, loading, and joke. What's happening here? Our component expects that the children prop should be a function that receives an object. This is where we can pass in common state and handler functions.
[03:11] Now let's import this component into our app and refactor our joke and joke advance components to use it. The first thing that we're going to need to do here is wrap our joke component with Chuck joke. Then we'll pass a function to it which becomes the children prop we looked at earlier and give it a destructured object for our parameter. We'll put all of our rendered HTML inside of it.
[03:37] We'll also replace the joke prop with category and pass it down to our Chuck joke component to be used by our fetch joke method. Next, I need to dismantle the higher order component here and just use joke directly.
[03:51] All right, that looks like it works. Let's go ahead and do the same for our joke advance component. Again, just like before, we'll wrap our component with a Chuck joke component, pass it a function with our properties, and then move our rendered HTML inside of that function.
[04:12] It looks like I may have forgotten to add the URL in the Chuck joke component. Let's go ahead and add that. Now that that's done, let's disconnect joke advance from our higher order component and just call it directly at the bottom.
[04:30] It looks our refactor from a higher order component to a render prop component is a success. Now we have this nice syntax to work with. It's clear where everything is coming from.
Are there cases where you'd prefer "traditional" HoCs over FAC?
Hey Platon, so far, not in my experience. I've been able to replace HoCs to FAC / Render Props and had good results. HoCs are a little easier to test because the rendered component is separate and you can just import and pass it fake props. With Render Props you have to mount and dig in a bit or make your render prop a component itself that you pass in and test.
Thank you, Andrew!
With some practice I am beginning to see the ease-of-use utility of FAC / Render Props compared to HoCs, especially when the meta-component has to take different dynamic props every time it's reused.
I am looking forward to your future videos! Please make one on your approach to testing FAC / Render Props too, if that's OK : )
The codesandbox has the code at the end of the video. If I want to follow along in the video I have to swap index.js for index.hoc.js. And then the index.hoc.js is not what you start with in the video... Ideally there should be one codesanbox that reflects the code at beginning of video and one at end.
The codesandbox has the code at the end of the video. If I want to follow along in the video I have to swap index.js for index.hoc.js. And then the index.hoc.js is not what you start with in the video... Ideally there should be one codesanbox that reflects the code at beginning of video and one at end.
Hey Thilo, a cool feature in codesandbox is the Current Module View
toggle (top right corner). Open index.hoc.js in the sandbox and toggle to "Current Module View" and it will use index.hoc.js instead of index.js.
I also fixed the HoC example to be closer to that which is in the video. Thanks for the feedback and watching!
Hey Platon! That's a great idea. I'll see what I can do!
Perfect!
It's also possible to utilize the Render Prop pattern by passing your component a regular property such as
render
.Example:
<MyComponent render={(...) => <div>Hello World</div>} />
I use the Function as Children (FAC) approach in this video because it's easier for me to visually understand and prettier does a great job cleaning it up. FAC's are still considered the "Render Prop" pattern because a Component's
children
are properties.See: Render Props for more info!