Adapter Pattern in React

Design patterns in software are helpful for many reasons, but two of the most important are that they give developers a shared vocabulary and offer solutions to commonly encountered problems. They are popularly used in OO languages, but not so often thought of in React projects.

Adapter Pattern in React
Photo by Rich Smith / Unsplash

Intro

Design patterns in software are helpful for many reasons, but two of the most important are that they give developers a shared vocabulary and offer solutions to commonly encountered problems. They are popularly used in OO languages, but not so often thought of in React projects.

In this article, I want to explore the Adapter pattern in React. In case you're not familiar with this pattern, I'll start with the classic object-oriented use case as an introduction. Then, I'll introduce an example of the Adapter pattern in action in a popular React library.

By the end of the article, I want to be able to answer these two questions:

  • What are some signals you should pay attention to when writing React components that point towards using the Adapter pattern?
  • What are some characteristics of components that use the Adapter pattern?

With that, let's get started!

What is the Adapter Pattern?

I'll be referring to the Adapter Pattern page from Refactoring Guru in this section. If you've never explored Refactoring Guru, it's a fantastic resource and is well worth your time.

From this article, we can extract some key points to help us define the Adapter pattern:

  • The Adapter pattern allows objects (or components) with incompatible interfaces to communicate with each other.
  • The adapter hides the complexity of converting between interfaces by wrapping one of the objects.
  • An adapter is especially useful when you can't - or don't want to - change the implementation details of an object to be compatible with another object.

The example used by Refactoring Guru is one where you have a source of stock data in XML format, and an analytics library that only takes in JSON data. The solution using the Adapter pattern is to wrap every interaction with the analytics library to take in the XML data, convert it to JSON, and send it to the appropriate methods of the library.

Now that we've defined the Adapter pattern, let's find a React example in the wild.

Example in the Wild

I use react-hook-form quite often because it simplifies the process of creating forms in React projects. It leans into normal HTML standards when it comes to form submissions and validation, which makes it feel natural to use.

The most basic usage of react-hook-form involves the useForm hook which returns a register function and a handleSubmit function. You connect the handleSubmit to the onSubmit event on your form, and connect the register function to your form fields. I've linked to a CodeSandbox below where I've set up a very basic form to illustrate this setup.

react-hook-form-simple - CodeSandbox
react-hook-form-simple by joerter using loader-utils, react, react-dom, react-hook-form, react-scripts

As you can see in the example, the register function itself is designed to be used with uncontrolled inputs that have props for onChange, onBlur, name, and ref.

const { onChange, onBlur, name, ref } = register('firstName'); 
// include type check against field path with the name you have supplied.
        
<input 
  onChange={onChange} // assign onChange event 
  onBlur={onBlur} // assign onBlur event
  name={name} // assign name prop
  ref={ref} // assign ref prop
/>
// same as above
<input {...register('firstName')} />

What if you're trying to use a 3rd party or custom input that is controlled and has non-standard event handlers? Luckily for you, react-hook-form includes the Controller component that is designed for such use cases. This Controller component is an example of the Adapter pattern in action because it has the ability to wrap any component with non-standard form behavior and make it compatible with other standard form fields.

Note one of our takeaways from the Adapter Pattern article from Coding Guru above:

👉
The Adapter pattern allows objects (or components) with incompatible interfaces to communicate with each other.

Here, the Controller component allows react-hook-form to communicate with basically any component that you would want to use in a form.

To see this in action, let's add on to the previous example form by adding a component to enter the username for the new user. Let's also pretend that we have some pre-existing component called UsernameInput that automatically checks to see if this username already exists in the system. This component is controlled since it automatically checks the entered username with the backend on every keystroke. It also has an onValidUsername prop that it only calls when a valid, available username has been entered.

In order to use this component in our form powered by react-form-hook, we'll have to use the Controller component to adapt the behavior of the UsernameInput component to be compatible with the form.

react-hook-form-simple-username - CodeSandbox
react-hook-form-simple-username by joerter using just-debounce-it, loader-utils, react, react-dom, react-hook-form, react-scripts
<Controller
  control={control}
  rules={{ required: true }}
  render={({ field }) => (
    <UsernameInput
      onValidUsername={(username: string) => {
        field.onChange(username);
      }}
    />
  )}
  name={"username"}
/>

In the above snippet, you can see where we've connected the field.onChange function from the render prop of the Controller component to the onValidUsername prop of our UsernameInput component.

This brings up a key feature of using the Adapter pattern in React. Render props! Render props make your adapter infinitely more reusable because it allows the adapter to wrap any component. Imagine a different Controller component that simply wrapped the form component instead of providing a render prop. In this case, you would have to have a different Controller component for every form component that needed adapting.

What signals in React apps point towards using the Adapter pattern? I think the most obvious is integrating 3rd party components into your application. Let's look at the MUI Datepicker as an example. This datepicker only accepts Date objects as props, but what if your server sends all dates as UTC timestamps? One possible solution using the Adapter pattern would look something like this:

<UtcTimestampAdapter
  timestamp={timestamp}
  render={(localDate: Date) => (
    <Datepicker defaultValue={localDate} />
  )}
/>

In this example, the UtcTimestampAdapter would handle converting the timestamp in UTC to the equivalent Date in the user's timezone, and pass it through the render prop where you can render the Datepicker. Now you can easily render the Datepicker with the correct date throughout your application!

Conclusion

I hope after reading this article you can spot opportunities in your own React projects to use the Adapter pattern. You may even find that you've been using the pattern without realizing it! The benefit of learning to recognize and use design patterns is that it gives you and your team a common vocabulary when discussing or reading code. If you found this article helpful, let me know and I'll write about other design patterns in React.