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

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