Advanced React Patterns Advanced React Patterns

Flexible Compound Component Solution

Check out a free preview of the full Advanced React Patterns course:
The "Flexible Compound Component Solution" Lesson is part of the full, Advanced React Patterns course featured in this preview video. Here's what you'd learn in this lesson:

Using the context API, Kent shows how to make the compound component more flexible with a shared implicit state by using flexible compound components when you have several components. These compound components need to share state, but the user does not need to know about it. Kent takes questions from students.

Get Unlimited Access Now

Transcript from the "Flexible Compound Component Solution" Lesson

[00:00:00]
>> Kent C. Dodds: All right. Let's go ahead and finish this exercise and answer any questions that you might have about this flexible compounds components thing. So I'm gonna bring in the exercise version of the test or the usage for the test, and run these tests. And we've got this siren thing that's saying the switch component is not getting past the right props.

[00:00:20] And we learned that's because this react children map, which is responsible for cloning these children and providing these props, can only be applied to the immediate children, not descendants of those children. So that's why the switch component isn't getting the right props. So let's go ahead and make that happen.

[00:00:39] The first thing that we're gonna do as indicated by Cody the koala bear is we make our ToggleContext, React.createContext. And here, this is going to be our default value for context. So if a consumer is rendered not within a provider. Then this is the value that consumer will receive, okay?

[00:01:03] And so, here we have this theme context example we're passing light as a string. And that's the value that this consumer will receive if it's not rendered within a provider which provides any other value. So for us, I want to provide on as false. And because the button needs to get access to the toggle, I need to also provide a toggle function and that will just do a no opt function.

[00:01:29] We could do some sort of validation or something in there as well, just say, hey, your rendering outside or something like that. But in our case, we're good with just an no-op function here. And then, we're gonna use the ToggleContext in our render method with a return ToggleContext.Provider, the value is going to be this.state and I will explain why.

[00:01:56] So actually here, I'll explain it later. Let's make value be this first, and then we'll just pass in this.props.children. And we can get rid of all these stuff.
>> Kent C. Dodds: Okay, stuff is not working, we're not even providing it to on and off right now. Because no longer do they need to know or to get those from props.

[00:02:20] They will actually get all that information from context. So I'm gonna do this fancy thing, [SOUND] just to get all this stuff done at once. So we'll say ToggleContext.Consumer, and this is going to accept a function that is contextValue. And that will return this value and then we'll close off ToggleContext.Consumer.

[00:02:51] Save that, and thank our lucky stars for a pretty year. And then instead of getting on from our props, we're gonna pull it from the contextValue. And we'll do that here also. Remove this, and then here we'll just rename this whole thing to props and we'll do contextValue.on and contectValue.toggle, okay.

[00:03:17] And everything is passing. So there's one more thing I wanna show about this, but before we do that, does anyone have any questions so far?
>> Speaker 2: It seems like the defaults being given the context just like hide problems, basically.
>> Kent C. Dodds: Yes.
>> Speaker 2: But you should never try to render?

[00:03:42]
>> Kent C. Dodds: Right, yeah. So there are some situations in context API's like this one for example, where it doesn't make any sense, [COUGH] to not have the provider. And in fact, the way that we would actually expose this is I'd probably put all of these in one file and then I'd only exposed the toggle component itself.

[00:04:06] And so, it would actually be impossible to use this components. Well, actually, sorry no, not impossible. I just do this, [SOUND]. And I'll prove I've broken everything. So yeah, I guess there may be a solution in the context API to say this is required. I know with the old version of the API, you use prop types and you could say context was required.

[00:04:28] I haven't actually looked into that to see if that's possible with the new API. But you could definitely in user land, you could do that where you'd say inside of here, like make the default something like no default or something like that.
>> Kent C. Dodds: So yeah, I feel like there are several other ways that you could do that.

[00:04:51] Good question, other questions? Okay, one person in the chat asked, just kind of for confirmation, would you put all of this in one file and then expose ToggleContext for people to use? Normally, that's similar to something that I would do. I generally, if I'm making a reusable component, I'm exposing context.

[00:05:16] I'm going to expose the provider or the consumers that case may be not the entire context object. I'd like to have more control over it. Later, we'll be doing something like this static Consumer, just to make things a little more explicit. In our case, we don't need to expose that.

[00:05:35] And this is kind of a key point to the compound components API, is that the state that's being shared between these components is all implicit state. So the user of these components doesn't actually see any of the state that's being shared between these components. And that's the general idea behind the compound components API.

[00:05:58] And when you know that you have a use case that's suited for compound components, is when you have several components that need to share a state, but the user doesn’t need to know about that, okay? Cool, so there’s one other problem with this and that is with the context API.

[00:06:16] Anytime this value changes to something else, to a different reference, to a different object, or anything else like that, then it's going to re-render itself as well as all of the consumers. And as we know render method can be called like man, many times and never actually change what the user is seeing, at least in many parts of the react tree.

[00:06:41] And so, by making this an object that we declare inside the render method, the value is changing every single time it's rendered. And so, every single time this is rendered, all the consumers will also be re-rendered. And so that could potentially be a performance problem. And so in general, I try to avoid doing this.

[00:07:00] And instead what I do is I pass to do this.state. And this.state is an object that doesn't change until things actually have changed, like I'm calling set state or something so it makes a lot of sense. The thing that doesn't really make a lot of sense is, [SOUND] to be able to provide the toggle, I actually have to add it to my state

[00:07:29]
>> Kent C. Dodds: Here, actually let me put that back where it was. And that feels weird to me, because a function in state, that's like odd. But you get used to it, because it's the best way to do this. And maybe in the future the React team will figure out a different way to accomplish this, but in conversation on Twitter with smart people, this is where we landed on how to do this.

[00:07:52] So one thing that you might have noticed, [COUGH] if you did do this is you're going to get an error, and this is because the way that class properties work. These are executed in order during the construction phase. And so, at the time that state is initialized, this dot, sorry, that should be toggle.

[00:08:15] This.toggle does not actually exist. It hasn't been initialized yet, it's coming after. That's why you have to move it down. And then everything is working, does that piece make sense? You can imagine this being inside of the constructor function and this in front of all these, if that is helpful.

[00:08:38] Okay, I think there's a question in the chat. Yeah, so if you're still using, so somebody's still asking about the constructor function. Constructor, so this is a default constructor function, args, super, args. This is, if you don't provide a constructor, this is the constructor that is provided for you.

[00:09:04] And so in our case, we could actually move all this here put this dot in front of these. And that's identical to what we had already in the first place. It's more code, so I don't like it. At least, it's more code that it doesn't provide any more clarity and so I don't like it.

[00:09:21] And so, I get rid of this stuff and just not use the constructor. I pretty much never use the constructor, like almost all the time because I can accomplish my purposes like this.
>> Kent C. Dodds: Any other questions before we jump in to the next one? Okay, so for this next one, we're actually gonna, yeah, question?

[00:09:48]
>> Speaker 3: Feedback.
>> Kent C. Dodds: Thank you. I'm gonna give you a high five at lunch. Yeah, so let's go ahead and fill out your elaboration, write down what you learned, and then update your test to say you submitted. And again, if you're tired of typing your email in every time you can just do node scripts auto fill email feedback.

[00:10:08] It will ask for your email address and pre-fill all of those for you.
>> Kent C. Dodds: Isn't code so cool that we could do that? I'm using a million modules to accomplish this simple task, but the fact that I could do that is pretty cool.