Write Compound Components

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated a year ago

Compound components give more rendering control to the user. The functionality of the component stays intact while how it looks and the order of the children can be changed at will. We get this functionality by using the special React.Children.map function to map over the children given to our <Toggle/> component. We map over the children to pass the on state as a prop to its children. We move the visual pieces of the component out into function components and add them as static properties to <Toggle/>.

Instructor: [00:00] In this usage example, we have this toggle component and then we have this three compound components that implicitly access the state of the toggle component and even a state updater like the toggle function.

[00:12] Let's start by creating a static on component that will accept an on and children. If it's on, it will render children. Otherwise, it will render no.

[00:23] We'll have a very similar off component, which will render no or children, and then we'll have a static button component which will accept on and toggle, and then it will just take the rest of the props.

[00:36] That will render a switch with on as on and onClick is toggle, and then spread the rest of the props. Those are the compound components.

[00:45] Now they need access to the state of the toggle and even the toggle click handler, so let's go ahead and return React.Children.map(this.props.children) and for each of these child elements, we'll return a React.cloneElement of the child element. We'll provide additional props like on from state and toggle from this.toggle.

[01:11] With that, things are functioning perfectly. It allows users to render things however they like, without us having to provide some sort of render configuration prop as an additional API for users to learn.

[01:24] In review, to make this possible we created a couple of static compound components -- on, off, and button -- which each accesses the toggle state and click handler that they needed, and receive those as props implicitly in the render method of the toggle.

[01:39] Now users of our component can render this however they like, and that state will be shared with these compound components implicitly.

Gustavo Sequeira
Gustavo Sequeira
~ 4 years ago

Hey, loving this course so far. I have a doubt about the render method, it'll be a little bit more neat if we isolate the arrow function in other method rather than having it on the render, right? Not sure if it works that way, I never use React.children.map

Thanks, Gustavo.

spencer
spencer
~ 4 years ago

Is there a git repo to this project by chance?

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Yep. Here it is: https://github.com/kentcdodds/advanced-react-patterns-v2/tree/egghead

spencer
spencer
~ 4 years ago

Awesome thanks Kent!

FateRiddle
FateRiddle
~ 4 years ago

Hi, Kent, what if you define those compond components inside Toggle component, then you can get access to this.state.on and this.toggle directly, how about that approach?

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Give it a try and you'll learn why it wont work that way :)

FateRiddle
FateRiddle
~ 4 years ago

Thank you for replying! @Kent C. This is the demo I wrote about the above mentioned compond components idea, and it works. https://codesandbox.io/s/0qrr0qxjzl. Any thought on if this pattern is viable?

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Oh yes, it does work if you change the API to a render prop. It's great ๐Ÿ‘ I've never found a use case for that myself. When doing a render prop API, they have everything they need to render whatever they like and I don't like imposing those components in them. So I've never done it myself, though I've seen other libraries adopt this pattern. I have no real problem with it and I think it could be useful for common use cases for your Component ๐Ÿ‘

Wenhan
Wenhan
~ 4 years ago

Great content! Why do we need a React.cloneElement here?

Thein
Thein
~ 3 years ago

Why do you want to put <Button /> at children? Off, On are status and good enough. Below is how I render in Toggle . And static and Toggle.On is little weird. Any better way without static ?

<div> {React.Children.map(this.props.children,(ctrl)=>React.cloneElement(ctrl, {on : this.state.on}))} <Switch on={this.state.on} onClick={this.toggle} /> </div>
Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

Hi Thein, That would get you to the same thing conceptually, but what if I as the user of your "reusable component" wanted to render my switch above the text? One of the goals of these patterns is to improve the flexibility and simplicity of components.

And static and Toggle.On is little weird. Any better way without static ?

"Better" is a matter of opinion. I like the static components because it communicates the relationship between the components, but if you don't like it you don't have to make it a static property, it can be a regular component just fine.

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

Hey Wenhan, try it without using React.cloneElement and you'll figure out why it's important :)

Thein
Thein
~ 3 years ago

If will be better, if you can produce distinct ui component in each pattern. Not just on/off switch...

Kwinten Li
Kwinten Li
~ 3 years ago

Hi, thanks for the the awesome videos!

I just tried doing some console.log to figure out what is happening here.

The output is like below.

The render methods are called 3 times, the first 2 are with the same state. (This is gone if I clone the project on codesandbox)

The last render is due to the async setState triggered inside the toggle method.

Not sure why it is triggered, I havent yet toggle the button.

Object {on: false}

Render triggered

Object {on: false}

Render triggered

Toggle triggered

Object {on: true}

Render triggered

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

Hi Kwinten,

My guess is it has to do with codesandbox. I wouldn't be too concerned about it in practice, but it's important to know that the render method can and does run many times and it's out of your control. Don't worry too much about it :)

Alex
Alex
~ 3 years ago

Okay, I think I get why we clone elements (it means we don't rely on the user of our reusable component to pass the necessary props, we do it ourselves?) but why use React.Children for the map when we can map over this.props.children directly? (I tried it and it works fine)

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

That's a great question Alex. Try changing it to this:

    <Toggle onToggle={onToggle}>
      <Toggle.Button />
    </Toggle>
Alex
Alex
~ 3 years ago

Okay, because this.props.children is sometimes not an array (when there's only one or none of them). Is that all React.Children() is used for?

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

That's a great question! Originally that's what I thought, but when I dove into the code I found it does quite a bit more. I never spent time on figuring out what exactly though. Start here: https://github.com/facebook/react/blob/69e2a0d732e1ca74f6dc5df9d0ddd0bf24373965/packages/react/src/ReactChildren.js#L326-L346

Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

I'll also note that preact always treats children as an array, so in downshift (because I want to support preact as well as react), we have a utility for this. The utility. Usage

Adam
Adam
~ 3 years ago

Hi, there is typo in code transcript "React.coneelement". Thanks for a great tutorial!

Kuba
Kuba
~ 3 years ago

This is truly awesome! Thanks Kent!

Joel Pablo
Joel Pablo
~ 2 years ago
Joel Pablo
Joel Pablo
~ 2 years ago

ah nm

Karthik Radhakrishnan
Karthik Radhakrishnan
~ 2 years ago

React.cloneElemement and React.Children is a good intro to more React's such not well known APIs. Thanks. Good way to start the course. I can see that the compound components are functional whereas the wrapper component is a class to support the static. Is it still not achievable via functional components?

Ryan
Ryan
~ 2 years ago

Similar question to Karthik, how does this example translate with the now purely functional methods of writing React?

Kent C. Dodds
Kent C. Doddsinstructor
~ 2 years ago

https://kentcdodds.com/blog/compound-components-with-react-hooks

Karthik Radhakrishnan
Karthik Radhakrishnan
~ 2 years ago

@Kent Thanks and yes I was able to try similar approach and apply that in my components in real projects. But the only issue I had was that IDE wasn't able to resolve/derive the static types of compound components and without TS/Flow more documentation was needed.

Apart from that, both render props and compound components were able to give more control over the components I created and was able to refactor existing code to add more flexibility in quick time. Thank you as usual.