Boost Readability in React with a custom <Div.InsertNameHere> Component

Boost Readability in React with a custom <Div.InsertNameHere> Component
💭
Edit 8/29: I made a mistake! I didn't have the Proxy's return element memoized. This was causing re-renders every time. I've fixed the code below. Enjoy!

I made a component for React w/ TypeScript that lets you create dynamically-named div components on the fly. For example, a <Div.Card>, <Div.TwoColumnGrid>, <Div.FormItem>, or anything else. Here's the code:

import { HTMLAttributes, useMemo } from 'react';

// This is a proxy that will simply pass-through to a regular `div`
// It ONLY exists to enable easy identifying of WHAT a div is.
// Ex. Use <Div.Card> or <Div.Box> to quickly be able to differentiate divs.
export const Div: Record<
  string,
  (props: HTMLAttributes<HTMLDivElement>) => JSX.Element
> = new Proxy(
  {},
  {
    get: (_, tagName) => {
      return useMemo(
        () => (props: HTMLAttributes<HTMLDivElement>) => <div {...props} />,
        [tagName]
      );
    },
  }
);

And to use this component, just import it anywhere in your code. You can use it like any other nested component, and choose any name you want after the period: <Div.Card>, <Div.Flexbox>, <Div.HorizontalDivider>, etc.

This can turn code that looks like this:

<div className="bg-gray-100 p-4">
  <div className="bg-white p-6 rounded-lg shadow-md">
    <div className="bg-blue-500 p-8 rounded-md shadow-xl">
      <div className="flex justify-center items-center">
        <div className="grid grid-cols-2 gap-4 bg-yellow-300">
          <div className="text-center bg-red-400 p-6">
            <p className="text-xl font-semibold">Layer 5</p>
          </div>
          <div className="flex justify-center items-center">
            <p className="text-2xl font-bold text-gray">Layer 5</p>
          </div>
        </div>
      </div>
    </div>
    <div>
      <div className="bg-orange-300 p-6 rounded-lg shadow-md"></div>
      <div className="bg-yellow-200 p-6 rounded-lg shadow-md"></div>
      <div className="bg-green-300 p-6 rounded-lg shadow-md"></div>
    </div>
  </div>
</div>

Into a much more readable set of code like this:

<Div.Background className="bg-gray-100 p-4">
  <Div.Page className="bg-white p-6 rounded-lg shadow-md">
    <Div.ContentCard className="bg-blue-500 p-8 rounded-md shadow-xl">
      <Div.Centered className="flex justify-center items-center">
        <Div.InfoGrid className="grid grid-cols-2 gap-4 bg-yellow-300">
          <Div.Title className="text-center bg-red-400 p-6">
            <p className="text-xl font-semibold">Layer 5</p>
          </Div.Title>
          <Div.Subtitle className="flex justify-center items-center">
            <p className="text-2xl font-bold text-gray">Layer 5</p>
          </Div.Subtitle>
        </Div.InfoGrid>
      </Div.Centered>
    </Div.ContentCard>
    <Div.Footer>
      <div className="bg-orange-300 p-6 rounded-lg shadow-md"></div>
      <div className="bg-yellow-200 p-6 rounded-lg shadow-md"></div>
      <div className="bg-green-300 p-6 rounded-lg shadow-md"></div>
    </Div.Footer>
  </Div.Page>
</Div.Background>

Why would I want this?

Without something like this, it's easy for your code to become a deeply nested stack of <div> tags that aren't easily understandable.

Now obviously the proper solution here is to split your component into smaller components. But, is this always the best solution?

There's a cost to creating a new component. You must define the component's interface, handle passing data and events, and maintain the abstraction long-term.

I'd argue that creating a new abstraction, while it is the "proper" solution, isn't always the right solution.

For someone like me, who lives and breathes in the startup world, it's quite often a mistake to make an abstraction too early.

There are many times when I'm building a prototype that isn't fully fleshed out. Or, a page is frequently changed from user feedback.

Those changes are almost impossible to predict. So, why would I build an abstraction for something where the underlying concept isn't stable?

If you have a similar use case, I think this is a great solution.

This proxy component simply lets you get a better understanding of what each <div> is accomplishing. It's simply a developer tool.

Plus, your new <Div.InsertNameHere> component still gets full TypeScript support and the ability to use any fields you can use on a standard <div>.

I also prefer this solution over using something like an id, class, aria tag or some other property on the div. I prefer it because:

(a) you get the identifier on the closing tag: </Div.Card>

(b) You don't affect the resulting HTML on the front end. This is purely a developer tool so it should only affect the developer experience.

(c) By searching for all the places you've used a <Div..., it gives you a really good idea of where to create abstractions in the future.

👋 Spencer