Skip to Content
All memories

The Trap of Premature Abstraction in React

 — #react#frontend#architecture

I used to have a rule: if I write the same UI code twice, extract it into a component. Sounds like good DRY (Don't Repeat Yourself) principles, right?

Turns out, this is a fantastic way to create a maintenance nightmare in React.

Here's the trap I kept falling into. I'd have a UserProfileCard and a CompanyProfileCard. They looked identical at first: avatar on the left, name in bold, subtext underneath. So, naturally, I'd abstract them into a <ProfileCard> component.

<ProfileCard 
  image={data.avatar} 
  title={data.name} 
  subtitle={data.role} 
/>

Beautiful. Clean. Reusable.

Then the product requirements change.

The UserProfileCard needs an "Online" status indicator. The CompanyProfileCard needs a "Verified" badge next to the name. Now my clean abstraction needs props:

<ProfileCard 
  image={data.avatar} 
  title={data.name} 
  subtitle={data.role} 
  showOnlineStatus={true}
  isVerified={false}
/>

A month later, CompanyProfileCard needs to fetch its own specific data when clicked, but UserProfileCard shouldn't. Now I'm passing callbacks and specific flags. Before long, my <ProfileCard> is a 500-line behemoth of conditional logic, rendering totally different things based on the permutation of 15 different boolean props.

It's the classic "Aha!" moment: wrong abstraction is far worse than duplication.

When we duplicate code, the cost is just typing more lines. We might have to update two places when styling changes. But when we create the wrong abstraction, the cost is cognitive overhead. Every time we need to add a feature to one use case, we have to carefully navigate the conditionals to ensure we don't break the other use cases.

My new rule is simple: Embrace duplication until the abstraction screams at you.

I'll write the same layout three, four, even five times. I'll let the code sit there, slightly messy, slightly redundant. Over time, as features are added and requirements evolve, the actual shared concepts become obvious.

Maybe the shared abstraction isn't a <ProfileCard> at all. Maybe it's just a shared <Avatar> component, and the rest of the layout is unique to each domain. You only discover these boundaries by letting the duplication live long enough to tell you what it wants to be.

Don't be afraid to copy-paste. Sometimes it's the most architectural thing you can do.