Make Compound React Components Flexible

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 3 years ago

Our current compound component implementation is great, but it's limited in that users cannot render the structure they need. Let's allow the user to have more flexibility by using React context to share the implicit state to our child <Toggle/> components. We will walk through safely defining childContextTypes, providing the value with getChildContext, and, on each of the components that need that context, we define contextTypes so that React will pass the context that is being asked for.

[00:00] We've given the user some flexibility. They can move the button over here if they want, or even between these two. They can order things however they like. But we don't give them structural flexibility. If I were to do a div right here and then put the button inside here and then try and click on it, nothing's going to happen.

[00:17] Even if I were to put the toggle off inside of there and move the button out, then I click on this, that toggle off is not going to disappear like it should. That's because at the time that this render function is called, we get these children. Those children only include the direct descendants of our toggle button. That is this div. But it doesn't include what's inside the div.

[00:41] It's almost like this toggle component needs to set up some sort of context for this part of the tree. That's exactly what we need to do. We need to use React Context so that all of these components inside have access to the state of the toggle button, so they know what to do.

[00:56] To do this, let's make this toggle context variable. We'll just give it toggle as the value here. Then we're going to apply this context to everything under the toggle component. If we go down to the toggle component here, we'll say static child context types is this object. The key is going to be that toggle context, and the value needs to be prop types.object.is required.

[01:25] Now we also need to include this prop types library. I'm going to go up here and add another script tag for that. We need to go back to our toggle component. As a class method, we'll have get child context. This is going to return an object. We'll do toggle context. That object is going to be the same thing that we had for our props before.

[01:51] We need to establish that each of these components depends on this context. We do with context types. Those context types will look just like our child context type, so I'll just copy that and paste that here.

[02:04] We're going to do the same thing for all of these. We'll say toggle off and toggle button. Now, instead of on coming from the props it's going to come from context. We can get rid of on from the props.

[02:20] Then we're going to say const on equals context at toggle context. We'll do the same thing for our toggle off and our toggle button. Instead of on and toggle coming from props, we'll just leave all the props as they are and then take context. Then we'll get const on and toggle from context at toggle context.

[02:46] The last thing we need to do is update our render function. We no longer need to clone any elements. We can actually just render a div with this.props.children and get rid of all this React clone element stuff.

[02:58] Now I can click here, and everything's working. We can structure things as nested or as all over the place as we want to. All of these components will have access to the state of the toggle component through context.

[03:11] In review, what we had to do here to make this work is you define child context types. Then you have a special key that goes on the context. That's going to share a name space with all of the other components that are using context in the tree.

[03:25] Normally it's good to give it some sort of long, odd name that you wouldn't expect to conflict with anything else. That value is going to be the prop types that are appropriate for the context.

[03:37] You define get child context with what value you want to have in context. Then on each of the components that need that context, they declare that they need it with context types referencing that same key. You get the state from that context at that key for each of these components.

[03:53] Then we updated our render method to just render the children in a div. That allows us to structure things however we like inside our app.

Krasimir
Krasimir
~ 4 years ago

Kent, thank you for making these videos. Really interesting to see these patters described so well. I have a question about the context in React. I see it as a way to solve dependency injection which right now is probably the most popular way of doing that. Do you know about some other mechanism for DI in the context of React.

Krasimir
Krasimir
~ 4 years ago

Also did it happened to you to see a conflict because of shared stuff in the same context?

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

Hi Krasimir! Honestly, I guess you could use context as a mechanism for DI. But honestly I don't think that DI is a good fit for React. Any reason you don't just use modules?

Also did it happened to you to see a conflict because of shared stuff in the same context?

I'm not sure I understand your question... If I were to place a <Toggle> inside another <Toggle> then the outer context would be overridden inside the nested <Toggle>.

Krasimir
Krasimir
~ 4 years ago

Thanks for the quick answer. Using modules only for DI decreases the testability of a component because we can't easily mock stuff. While with a context we may write a complete setup. I'm not a big fan of the context but unfortunately that's probably the most standard way of passing dependencies around. Last time when I checked Redux was relying on context too within its connect HoC.

Regarding the conflicts: yep, something like this. I hit that problem once where one of the inner components was messing around with the context and it broke it by replacing a bit inside. Anyways, I guess it is more or less matter of naming.

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

Hello again Krasimir!

Using modules only for DI decreases the testability of a component because we can't easily mock stuff.

First off, if you use Jest, then mocking is actually quite simple using Jest's built-in mocking capabilities.

Also, here's a handy trick that Ryan Florence showed me once:

import xLib from 'x-lib'

function MyComponent(props) {
  // use props.xLib instead of the xLib you import
  // when the app is running, we'll fallback to the default props
  // when the tests are running you can pass the mocked
  // version of xLib in as a prop.
}
MyComponent.defaultProps = { xLib }

Pretty clever I think. But honestly I'd just use Jest to mock modules because it's really good at it. Good luck!

Krasimir
Krasimir
~ 4 years ago

Wow, that trick is freaking awesome. As for using Jest we are indeed planning to migrate to it soon. The thing is that we already have a huge codebase so translating everything to modules will be quite expensive.

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

Don't migrate yourself! Make the computer do it for you ✨

Learn more here: https://facebook.github.io/jest/docs/en/migration-guide.html

Krasimir
Krasimir
~ 4 years ago

Ah nice. Thanks for the link.

Cam Kida
Cam Kida
~ 4 years ago

These lessons are gold... thanks again Kent!

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

I'm glad you like them cam!

Roger-Tong
Roger-Tong
~ 4 years ago

Ken, it's a really good instruction. I just have one concern: in the contextTypes definition, isRequired seems to have no use. I removed isRequired from the chain and commented out getChildContext method. There was no warning after refreshing the page.

Michał Murawski
Michał Murawski
~ 4 years ago

I am just curious about one thing: In official React docs, it stands we should not use context, but here we getChildContext for, what seems to be simple components and states. Pros/cons maybe?

Rafael Bitencourt
Rafael Bitencourt
~ 4 years ago

Very nice discussion too

Jack
Jack
~ 4 years ago

I am just curious about one thing: In official React docs, it stands we should not use context, but here we getChildContext for, what seems to be simple components and states. Pros/cons maybe?

Thanks for the awesome lesson Kent. Can you please comment on this?

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

Hi Michał and Jack!

Actually my newsletter tomorrow will talk about context at length. Maybe you'll be interested in subscribing? http://kcd.im/news 💌

Jack
Jack
~ 4 years ago

Already a subscriber 😅 Just finished reading it. Really excited to have it as a "first-class" feature(when released). Context was some "theoretical" concept that I never felt confident about implementing practically. It's gonna help us overcome the pains of "props-drilling" and allow us to really harness the power of re-usable components. Thanks Kent 👍

siffogh
siffogh
~ 4 years ago

Hi Ken, I was wondering why do you use a key in the context? (in this case you used 'toggle') Is it just so that you can differentiate between multiple contexts in the component that receive the context? Is it a bad practice to use context without a key?

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

Hi siffogh,

Context shares a namespace with other context, so the key needs to be unique. You can't avoid it. It's required to use the API.

s2_fe
s2_fe
~ 4 years ago

Hi Kent! https://reactjs.org/docs/context.html

but what about the Docs? As far as I see it's not recomented to use i.e. "If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React."

but I guess you are aware of that. so my question would be: why did you decide to recommend/use it in this case?

thanks! :)

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

Hey @s2_fe, That's a fair concern. Guess what though! A few weeks after I published this course, the React team landed on a context API that they like and it will be published in the next minor version release of React. I posted the blog post about the new API here. It's a big improvement and migrating from the current API to the new one will be pretty easy (both APIs will work until React 17 is released, which is farther into the future). I do plan on updating this course to the new API when the new one has been officially released.

So I'd say: definitely use context. If you want to start using it today, you can try create-react-context

Good luck!

s2_fe
s2_fe
~ 4 years ago

wow, that was a fast answer :) and helpful indeed .. i'm looking forward to that update & thank you for your work!

Vlad
Vlad
~ 4 years ago

This video convinced me to consider compound components in the future. The approach where we clone elements and place clones into the tree instead of the original source seemed very bad in my opinion. The context approach feels far more simple to follow. Thanks!

Shane
Shane
~ 4 years ago

Hey Kent! Thanks for this amazing series! Now that context is officially supported, will we be seeing an updated version of this lesson! This implementation seems much more verbose then the updated API, so it'd sure be great to see something that would be a little cleaner in production.

Thanks again!

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

As mentioned before:

I do plan on updating this course to the new API when the new one has been officially released.

Stephen James
Stephen James
~ 4 years ago

So without

ToggleOn.contextTypes = {
  [TOGGLE_CONTEXT]: PropTypes.object.isRequired,
}

the component won't get the context parameter? Does it only get the context that it specified in the contextTypes?

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

Correct Stephan. There's a new, more official API for context now: https://blog.kentcdodds.com/reacts--new-context-api-70c9fe01596b

I'll be updating this course with the new API soon.

Platon
Platon
~ 4 years ago

Hey Kent! How's the course update coming along? I keep checking for it regularly, this has been one of my favorite courses on this website and I love your teaching style!

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

Hey Platon, All the videos have been uploaded. They're currently creating the enhanced transcripts and art for the updated course :) I don't decide when it's released so I can't tell you much more than that. Should be soon though!

Platon
Platon
~ 4 years ago

Glad to hear it, thanks so much! :)