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.
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
childrenor 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