Check out a free preview of the full Advanced React Patterns course:
The "Control Props 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:

Kent walks through the solution to the Control Props Exercise by changing the generic control props solution to work with more complex components that integrates with state reducer. Kent answers questions from students.

Get Unlimited Access Now

Transcript from the "Control Props Solution" Lesson

[00:00:00]
>> Kent C. Dodds: All righty, so let's switch these two and run our tests. And, we've got a bunch of errors. So onToggle's not a function. All right, that's nonsense. Let's figure this out. So we're combining control prompts with our existing solution. The reason I say it's with a state reducer is because they actually cover similar use cases.

[00:00:27] The control state reducer is actually pretty much unnecessary if your have control props. The reason that I like to put them together though is because if I need control props for some reason, then a state reducer is probably a simpler way to handle some use cases. And so, with control props people have to put that state somewhere like where this on state is gonna live.

[00:00:55] It's gotta exist as a state property of some other component and with the state reducer, you don't actually have to do that necessarily. And so, having them together makes it so users have a little bit more choice on how they're using your component. But yeah, if you want to be able to update state of a component from the outside and like totally uncontrolled by the toggle component itself controlled props after that.

[00:01:24] So specifically in our case where we wanna synchronize these two, this component state is getting updated from the outside in. And a state reducer can't do that, but a state reducer is a little bit simpler from a user standpoint for other use cases, so I like to have them both.

[00:01:47] So before we didn't have an on toggle default because this component is completely worthless without being able to know when the toggle happens. But now they can know when the toggle happens from a new prop that we're going to expose called on state change. And so we're gonna add a default for both of those and there'll just be no op functions.

[00:02:07] And if we're using typing then we might be able to say, you have to have one or the other. That type definition would be kind of silly looking back. But yeah, that's generally the idea. You want one or the other or both. It's fine to have at least one.

[00:02:26] Okay, so we've got our defaults. Now it's at our, it's controlled and here we'll accept a prop. And we'll return this .props [prop] is not equal to undefined. And then we'll have that getState that we had before, except this one is gonna be a little unique because when we are using the state inside of our updater function, we need to get state from its canonical source.

[00:03:02] And that could be from prop sort, it could be from internal state, it could be a mix of the two. And so, this getState function will actually set a state argument. And that will default to this.state, so all the other places that are using it can just call it, and it will default to this.state.

[00:03:20] But when we call it inside of our updater function, we'll be able to provide the state from our updater function. So in this case, we're gonna return object.keys or entries or whatever you want to do. I'm just going just do keys state. We'll do entries. And with this, we'll reduce that into an object.

[00:03:44] So that's our new state object right there. And so we'll call this new state, that's our accumulator. And then we'll destructure the key and the value from our entry. All right, sweet. So then we’ll need to return the new state. And we’ll say if this .a is controlled the key.

[00:04:11] So if that particular part of state is controlled then we’ll say new state at that key is gonna be this.props at that key. Okay so if this state is being passed in from props, then that's our new state is going to have its value from props. Otherwise, it'll get it from state.

[00:04:36] And actually, that's what the value's gonna be so, tada.
>> Kent C. Dodds: Okay? Cool, so that is get state. An observant question was asked and I wanna share that really quick why you can't do this. Somebody suggested perhaps we could do this, returnobject.assign
>> Kent C. Dodds: Just, all right, this is our new state.

[00:05:03] And then state and this.props and then this.prop wins in the case of conflicts. The problem with that is this.props has all of these extra properties on it and so those aren't part of our state. We'd have to pluck those things out. So the amount of code sixes, so you can do it either way.

[00:05:23] So create the state object with all the props and then remove the extra props or something. But yeah, I like this better. So we're only dealing with stuff that's in our state. Okay, cool, so let's get rid of that. Make sure we're getting this all done. So anywhere that we're using state, we want to use like combined state.

[00:05:52] So I'm gonna make combined state this doc get state with the state. So in our updater function, we're past the current state in this batch of set state calls, and we're gonna call getState for that state in the batch of getState calls. And so now it's combined between the state that's coming internally, and the state that's coming from props, and that's what we'll use for everything else.

[00:06:19] So we'll call updater function. With the combined state, we'll call our state reducer with that combined state. And yeah, so far that's all the updates we needed to do there. Okay cool, get rid of that. Okay, so now things are, this is where things are gonna get a little tricky.

[00:06:44] And yeah, this is actually the reason that I was up till two last night cuz I was rethinking things and spent two hours working on it and then decided this was the best way. So code very similar to this is in down shift and it's working great. So we're gonna create a new variable up here called all changes, and rather than call or create a new variable called reduce changes, we're gonna just assign all changes to that.

[00:07:12] So this allChanges, by allChanges it means it includes the type and we're gonna need that here in a second. So let's get rid of this, then we'll do all changes, so now were removing the type from all changes to get only changes and we return that.
>> Kent C. Dodds: Let's see, [SOUND], sorry, I think I confused myself for just a second.

[00:07:46]
>> Kent C. Dodds: Right, so because our component is only managing the state for which it is managing. So in an effort to avoid unnecessary re-renders, we'll chip away this or this onlyChanges, or really, we'll just chip away the allChanges object down to only the stuff that's being managed by ourselves, because that's the only state we wanna update.

[00:08:13] So in the case of downshift, if you are controlling the is open state, and somebody clicks on the menu thing, we wanna let the owner of this component know, hey, the user clicked on the menu so I suggest that you open the menu. But I don't wanna manage that state myself, so I'm not going to, I'm calling such state, but I'm gonna return null instead of triggering re-render.

[00:08:39] Because it maymbe unnecessary, maybe the owner of this says, hey, when the user clicks on the thing because some other like something else is going on, I don't actually want the menu to open up again. And so, that's why we're gonna split up all of these allChanges between changes that are coming from or changes that are controlled by our internal state and changes that are controlled by prompts.

[00:09:04] And then only those that are controlled by state internally are those that will actually return from our set state call. Does that make sense? There's a lot of words so I'm sorry to be talking so much about state. I probably use that word a lot. So to accomplish this, we're going to, let's see.

[00:09:31] Rats, I'm just gonna look at it really quick to remind myself. Yeah, that's what I call it, non controlled changes. Okay, so it actually looks kind of similar to our get state. So nonControlledChanges, oops equals Object.entries or keys or however you wanna do this of all changes. Actually we'll just do our combined state, because ultimately that's all that we really care about.

[00:10:02] All changes includes the type, and we don't wanna return the type anyway. We could actually do this and do only changes, but it really doesn't matter. Because all we really care about are actually the keys. So there are like a lot of different ways you can do this, but the general idea is let's just get the changes that are not controled.

[00:10:24] Okay, so then we'll reduce this into another object. And so we'll just call this, yeah, accumulator and that's going to accept our state key and then well start out with an empty object and we'll return that accumulator. Let me give myself some more room here. Okay, great, so with that, we'll say if this dot or let's see, is controlled state key.

[00:11:03] So if this particular piece of state is controlled, then we don't wanna actually do anything. So we'll do the opposite of this. So if it's not controlled, then we'll add the accumulator [stateKey] = allChanges[stateKey]. And this is what we're gonna use here, that's what we're actually going to be returning.

[00:11:32] Okay, cooli and then now we can update the state. And we only update the state that's not controlled and so that's great. But the consumer that's controlling this state needs to know when should I update state. The user's done something. I need to know based off of your own internal logic, when should I think about updating state?

[00:11:56] And so that's where we modify this slightly. [COUGH] So and this is why we created this allChanges variable up here. So we can access it inside of here. So we'll say, this.props.onStateChange{allChanges}. So now it knows not only what changes took place but because this allChanges includes the type, it will also have the types so they can make decisions on whether they actually want to update their own version of that state or not.

[00:12:32] And it includes all the changes, not just those that are controlled because it's probably useful. And in addition just for fun, we can also pass this.getStateAndHelpers because I added that to downshift and I remember there was some point that I was really happy that I did. I can't remember why, but it's just kind of a useful thing, like based on state, I want to call the toggle again or something like that.

[00:12:59] So it can be useful for some cases. Okay, let's see if that passes our tests. Nope, I think we're still missing a couple pieces. All right, this.state, all of these should be this.getState. There, like magic, [SOUND]. All right, so what questions do you have about this?
>> Kent C. Dodds: Yeah.

[00:13:39]
>> Speaker 2: Do you,
>> Speaker 2: Always use like on line or is it 49 there, objects iq's that reduce,
>> Speaker 2: You're doing this to get an array out of objects so that you can reduce it, and then sort of combining it back together into an object? I know like low dash or underscore have like.

[00:14:10] A reducer that you can use directly on an object. Do you ever use anything like that? Maybe not when you're developing a library, but.
>> Kent C. Dodds: Yeah, exactly, that's exactly right. Good question, though. So yeah, low dash, I'm sure there are 30 MPM modules that all can do this, too.

[00:14:25] So if this were really complicated, I might find a small library that could do that. But just like you said, when I'm using a library or when I'm writing a library then I wanna just kinda use the raw APIs. It's not like too much more complicated than using reduce from what Azure is like, reducer.

[00:14:45]
>> Speaker 2: It's just a little [INAUDIBLE]
>> Kent C. Dodds: Yeah, but it does read a little bit nicer so I can see how that could be valuable. If you are doing this within your application using low dash then I wouldn't have any qualms with somebody wanting to just use low dash reduce or something.

[00:15:00]
>> Kent C. Dodds: Yeah, good question.