Validate Compound Component Context Consumers

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated a year ago

If someone uses one of our compound components outside the React.createContext <ToggleContext.Provider />, they will experience a confusing error. We could provide a default value for our context, but in our situation that doesn't make sense. Instead let's build a simple function component which does validation of our contextValue that comes from the <ToggleContext.Consumer />. That way we can provide a more helpful error message.

Instructor: [00:00] One thing we need to consider when using React to create context is what would happen if somebody renders a consumer outside of a provider. We can do that inside of here by swapping out our toggle with a div, and a div here.

[00:14] We're going to get an error, "Cannot read on of undefined." That's happening because here, we're destructuring something that is actually undefined. The context value here is not defined because we're not providing any sort of default value, and we're not providing a value via a provider.

[00:32] We could solve this problem in two ways. First, we can provide a default value here -- provide on is false, and toggle is just an empty arrow function. That would avoid the error, but the component doesn't work at all, so we'll just use some validation.

[00:47] I'm going to make a new component called function toggle consumer. That's going to take some props, and then this is going to return toggle context consumer.

[00:58] As the child function, it's going to get the context, and it's going to return props.childrencontext.

[01:09] So far, we haven't done anything different. Let's just swap out all of these for our new function, and everything works exactly the same as it had before.

[01:18] Now let's do our validation. We'll say, "If there's not context, then that means that we're rendering this outside of a provider."

[01:26] That's not allowed, so we'll throw a new error that says, "Toggle compound components must be rendered within the toggle component." With that, we get a useful and actionable error message. Let's go ahead and fix this to be toggle again, and the button is still working.

[01:48] In review, the problem we're trying to solve here is that the toggle context consumer in our situation doesn't make sense to be rendered outside of the toggle context provider, so we're adding some validation to make sure that the context value does exist.

[02:02] If it doesn't, we'll throw an error. That ensures that people are rendering the toggle context consumer within the toggle context provider.

[02:09] There are some situations where having a default value for a context does make sense, but for compound components, that generally isn't the case. Having some form of validation can help users of your compound components avoid confusing errors.

flowstate
flowstate
~ 4 years ago

I'm sorry for asking something off-topic, but: could you possibly share how you set up some of the convenience functionality / hotkeys you show during this lecture? In this video, you chose a single ToggleContext.Consumer node, then selected all the matching tags, and renamed them all. In a video earlier you did some quick tricks (possibly just good editing?) to surround an arrow function's code with parentheses. I'm new to JS in IDEs, and am looking for any way to increase my productivity.

On topic, thanks for the exceptionally clear and useful videos.

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

Hi flowstate, Maybe sometime I'll make that the subject of a devtip. I'm glad you like it :)

Nazariy
Nazariy
~ 2 years ago

props.children is a function? How come we can call it with the context?

Rafal Pastuszak
Rafal Pastuszak
~ 2 years ago

@Nazariy props.children can be either and array of children or a single item. In this case props.children is a function, e.g.: { contextValue => (contextValue.on ? children : null) }

Given:

<div className='parent'>
  <div className='child' />
</div>

parent's props.children will be <div className='child' /> not [ <div className='child' /> ]