DEV Community

Cover image for Make your React components more flexible using polymorphism
Julio Xavier
Julio Xavier

Posted on

Make your React components more flexible using polymorphism

When we're working with React, it's natural to break the UI into a component hierarchy. So you need to reuse an element across your app, you make it a component.

As requirements evolve and new UIs are created, you may need a piece of UI that is almost the same that you created before, but in a grid layout. If you decide to handle that inside the component, things can become quite tricky. Let's look into an approach to solve that problem by using Polymorphic Components (a.k.a. the "as" prop).

Take for example an "Input" component that accepts a label prop and renders that label above the text input.

// the input code
export const Input = ({ id, label }) => {
  return (
    <div>
      {label && <label htmlFor={id}>{label}</label>}

      <div>
        <input id={id} type="text" />
      </div>
    </div>
  );
};

// using it in some form
export const Form = ({ id, label }) => {
  return (
    <div>
      <Input id="input-1" label="My Label" />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Single Input

Cool, it's done. Now you can add your custom styles and reuse it everywhere!

A few weeks later, you're building a new form, and now there is a grid of labels and inputs, the labels need to be horizontally aligned with the text inputs and each text input needs to be aligned vertically with the other inputs:

Multiple inputs aligned

How do you handle that? Add a boolean condition? Create a new component? But how do you make sure that the labels will align? Polymorphism to the rescue!

Let's enable the as prop for our component and use it with a Fragment:

// enable an `as` prop that defaults to "div", the element used before
// if an element is passed, that element will be used instead
export const Input = ({ id, label, as = "div" }) => {
  const Tag = as; // use that as the tag for your wrapper element

  return (
    <Tag>
      {label && <label htmlFor={id}>{label}</label>}

      <div>
        <input id={id} type="text" />
      </div>
    </Tag>
  );
};

// now, in your form, set the "as" prop as a React Fragment
export const Form = ({ id, label }) => {
  return (
    {/* The `Grid` class has our CSS Grid Layout */}
    <div className="Grid">
      <Input id="input-1" label="My Label" as={Fragment} />
      <Input id="input-2" label="Other Label" as={Fragment} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

As the fragment has no effect on the resulting DOM, we are able to apply our grid layout as if all the labels and the inputs were used directly in the div that has the "Grid" class. That avoids adding more conditions to your components and making sure that all Inputs align correctly.

Thanks for reading!

How would you solve that layout problem? Have you used polymorphism before?

Let's keep in touch! Here's my Twitter.

Top comments (1)

Collapse
 
moutafatin1 profile image
moutafatin1