Improve the usability of Control Props with state change types

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 2 years ago

Our onStateChange handler is great, but it's limited in capacity because we don't know why certain state is changing. By adding stateChangeTypes to our component, it allows consumers of our component to have more insight into how to respond to certain changes.

Instructor: [00:00] We've modified our render method here to allow for two buttons for on and off. This way, you can click on this buttons to explicitly turn on and off these toggle switches. Right now, by clicking off and on, it'll switch this toggle switch and the other one, because we're controlling that state in our usage example by passing both on to on, and handle state change updates the on state when either one of those changes.

[00:25] Because this state is being controlled here, it's being updated for both of the switches. Now, let's say we wanted to have more fine grained control over what happens in handle state change based off of whether they clicked one of these buttons, or if they clicked on the switch.

[00:40] We could add props for on on click and on off click. But let's see how we can add a little bit more information to changes so all of our change handling can happen in this handle state change. We could say something like, if changes.type is toggle on or changes.type is toggle off, then we'll return, and we won't allow those buttons to be clicked at all.

[01:04] Let's go ahead and make this work. We'll need to provide a type property onto the changes object. Let's go up here to the toggle function definition where we're calling internal set state. This object is getting passed to the stop prop, stop on state change right here.

[01:19] Here in our handle off click and our handle on click, we could pass a type, toggle off, and another type here, toggle on, and with that now, we'll pull out the type, and we'll just forward it along. And now, these buttons don't function.

[01:35] I'm going to go ahead and copy and paste in some logic in here. Here, we store whether the last change was a button, and then we determine if this new change is a button change. And if the last one was a button change and the new one is a button change, then we'll update the state.

[01:50] We'll also update the state if it's not a button change because the user clicked on the switch. With that, we can switch on, switch off and then click on on twice to turn it on and then off twice to turn it off. And that's a use case that's enabled for us by adding a type to our changes object.

[02:07] Let's go ahead and clean up a few things. Right now, we don't have a type for the toggle here. Let's go ahead and add one. We'll say type is toggle, and then we can be more explicit down here where we'd say instead of is not a button change, we just say changes type is toggle.

[02:24] And then another thing we need to worry about is now we're passing the type as part of this object to update our state so the type property's actually part of our components state now. This is a problem because if I click on on and then off, the state didn't actually change but the type updated, and so we get a re-render of the component even though nothing actually changed.

[02:44] It'd be nice to avoid this unnecessary re-render. Let's go up here in our internal set state method, and on this changes object that we're using to get our non-controlled changes, that non-controlled changes object is going to have the type because the changes object has a type.

[02:59] Let's go ahead and create a new variable, where we'll pluck off the type. We'll call that ignored type, and then we'll take the rest of the changes and we'll call those only changes. And that'll equal the changes object.

[03:11] And then, we'll take the only changes and use that instead of the changes object. Now our reducer is not operating on an object that has a type, the non-controlled changes will never have a type property, and we'll be able to avoid unnecessary re-renders.

[03:25] One other cleanup refactor I'd like to do here is I don't like it when people have to hard code strings that have some sort of significance. Not only do they have to remember what the strings are, but their purpose isn't altogether explicit.

[03:37] What we could do instead is let's add a state change types static property to the toggle class, and that'll be an object that contains all of the different types of changes that this component can make. Here, will say toggle on, and then we'll also for the toggle off state change type stop toggle off. And for this type, we'll call it toggle state change types.toggle.

[04:02] Let's go ahead and refactor above to make this more explicit API possible. First, we'll add the static property, static state change types is this object, and it has a toggle on, and the value of this can be anything now because we're not hard coding it anywhere.

[04:18] I'll just call it toggle on, and then we'll have a toggle off and a toggle. Next, we'll update our references of these types, instead of type is toggled, we'll do toggle state change type stop toggle, and we'll do something similar for both of these.

[04:35] And with that, everything is still working, except now, users of our toggle component will have more power in their on state change handlers.

Cameron Yick
Cameron Yick
~ 3 years ago

Do you prefer to put your constants on the class versus defining an external mapping dictionary (Like const StateChangeTypes = { toggle: "_toggle", ...etc } (or enum in Typescript)?