LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #37: The Reusability Trap – When a 'multi-purpose' Component becomes a burden
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #37: The Reusability Trap – When a 'multi-purpose' Component becomes a burden

The story of trying to create a 'use-everywhere' component and ending up with a maze of props that no one wants to touch.

TP
Truong PhamSoftware Engineer
PublishedJuly 15, 2024

1. The Context

A new project started, and my team and I were building a small Design System. With the mindset of someone who hates repeating code (DRY - Don't Repeat Yourself), I set an ambitious goal: "I will write a single Table Component for the entire application. It must do everything: pagination, filtering, sorting, drag-and-drop, inline editing, and display all types of data."

Three months later, that Table became the terror of the team. It all started when a developer wanted to add a tiny feature: "Just show an icon when hovering on a row." I looked at UniversalTable.tsx; it had ballooned to 2,000 lines with nearly 50 different props. I spent the whole morning just trying to figure out where to put an if check so I wouldn't break 10 other pages using this same Table.

2. Foundational Knowledge

In software development, there is a concept called Abstraction. Reusing code is good, but if you abstract too early before seeing enough real-world patterns, you fall into the trap of Premature Abstraction.

A good component should follow the Single Responsibility principle. When you force a component to "know too much" about different contexts, you are creating an invisible coupling. Instead of helping the team move faster, you force them to learn a complex "language" just to render a table.

3. The Specific Problem

The system began to scale. We needed a table for Product Categories, a table for Payment History, and a table for User Management. Each table had a slightly different UI requirement.

Because of UniversalTable, my code was full of if-else blocks in props:

// An example of the chaos
<UniversalTable 
  data={orders}
  showCheckbox={true}
  isEditable={user.role === 'admin'}
  customHeaderColor={pageType === 'finance' ? 'blue' : 'gray'}
  renderRow={(row) => {
    if (pageType === 'inventory') return <InventoryRow data={row} />
    if (pageType === 'billing') return <BillingRow data={row} />
    // ... add 10 more if checks here
  }}
/>

This is a clear sign of poor design. The logic of each page was being "pumped" back into a generic component, stripping it of its independence.

4. How I Handled It

I spent a week trying to "save" it by adding even more props. I assumed that if I just passed more callback functions, everything would be fine. But the more I added, the more performance dropped because every time it re-rendered, the giant table had to re-evaluate dozens of logical conditions.

Finally, my solution was: Destroy the multi-purpose. I broke UniversalTable into more atomic components: TableBody, TableHeader, TablePagination. Then, on each page, I assembled them. If the Order page needed specific logic, I wrote a dedicated OrderTable using those atomic parts.

5. Trade-offs

The decomposition approach (Composition over Inheritance) meant I had to write more files. Instead of one called UniversalTable, I now had 10 table files for 10 different pages.

Cons: If I wanted to change the border color of all tables, I might have to edit more files (if I didn't use CSS variables well). Pros: Maintainability skyrocketed. A Junior could go in and fix OrderTable without fearing they'd crash BillingTable. Each code file was now only about 100-200 lines long, extremely easy to read.

6. Lesson Learned

After that incident, I keep this in mind: A little duplication is better than a wrong abstraction.

  • Advice: Don't try to make your components too "smart." Make them flexible through Composition. Instead of receiving 50 props, let it receive children or sub-components.
  • When to apply: Write 3 separate components for 3 different pages first. By the 4th, if you see they are 90% identical, then think about grouping them into one.
  • Self-reflection: Am I over-engineering by breaking it down too much? Perhaps. But in a team environment, "clarity" always wins over "conciseness."

Juniors are often afraid of repeating code for fear of being judged as "amateurs." But in reality, Seniors fear nothing more than a mess of indiscriminate grouping that no one dares to touch.


Notes on the day I learned to let go of perfectionism in reusability.

Series • Part 37 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #38: Micro-frontend Reality Check – When theoretical glory meets harsh reality
Blog #36: Touching Legacy Code – When a refactor effort becomes a disaster
32Blog #32: Friday Afternoon, 800 Lines of Code, and a Miscalculation33Blog #33: 10 PM and the 'Infinite Loop' of Punishment34Blog #34: My Machine Runs Smooth, the User's Machine Smokes35Blog #35: Clean Architecture – The Dream and the Harsh Reality36Blog #36: Touching Legacy Code – When a refactor effort becomes a disaster37Blog #37: The Reusability Trap – When a 'multi-purpose' Component becomes a burdenReading38Blog #38: Micro-frontend Reality Check – When theoretical glory meets harsh reality39Blog #39: The 'Will Fix Later' Promise and the Compound Interest of Technical Debt40Blog #40: Don't try to be smart – Write code for humans, not for machines41Blog #41: The 2 AM Panic and Infinite Question Marks42Blog #42: When the Backend Changes the Schema and the Fragility of the Frontend
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles