The Controllable Pattern

Learn how to implement controllable behavior in higher-order components.

We'll cover the following

Components in React are commonly described as controlled or uncontrolled with respect to some variable. If that variable is passed down to it through props, the component is controlled. If that variable is managed as state, the component is uncontrolled.

In its original incarnation, Carousel was uncontrolled with respect to slideIndex. With the HasIndex refactoring, the core of Carousel is now controlled, but the version of Carousel that this library’s users will consume is still uncontrolled. Nothing outside of Carousel can modify its slideIndex because that variable is kept in state.

Suppose you removed the HasIndex wrapper to make Carousel controlled. That would make the component more versatile since the user could change the slideIndex freely. But it would also make it more cumbersome since the user would have to implement their own slideIndexDecrement and slideIndexIncrement handlers.

What if you could get the best of both worlds? That’s what the controllable pattern offers. As its name suggests, a controllable component is one that can be optionally controlled. If the user chooses not to control the variable in question, then it functions as an uncontrolled component. The controllable pattern is exemplified by React’s own wrappers around form elements, e.g. <input>

In this section you will modify HasIndex to make the slideIndex on Carousel controllable.

Implementing controllable behaviors

Making Carousel controllable entails accepting a slideIndex prop with the following behavior:

  1. If slideIndex is undefined, it continues to function the way it always has, changing slideIndex internally when the Prev/Next buttons are clicked.
  2. If slideIndex is defined, it overrides any internal state.

One implication of this is that the Prev/Next buttons will no longer be able to change the effective slideIndex if the prop is set. Instead, they should trigger a change event, conventionally named onSlideIndexChange. This event gives whoever is controlling the Carousel the option to update the slideIndex prop.

This is a common source of confusion, so it bears emphasis. When onSlideIndexChange is called, it does not necessarily mean that the slideIndex has changed. It can actually mean two things. In the uncontrolled case, it means slideIndex (the internal value) has changed. In the controlled case, it means slideIndex would change if the component were uncontrolled, and the controller has the option to change the slideIndex prop accordingly. A fundamental rule of React is that components have no power to change their own props.

  • You can express all of these requirements as tests against HasIndex:

Get hands-on with 1200+ tech skills courses.