When Abstraction Becomes Over-Abstraction
By Tharindu Kumarasiri · June 10, 2025 · 5 min read
I used to think making the code “elegant” is number one priority. I spent years building “perfect” systems where every piece of logic was extracted into layers of pure, reusable custom hooks. I thought I was being efficient. In reality, I was building a maze that only I had the map for.
We've all seen it: you open a simple Profile.tsx file and find yourself scrolling through 200 lines of “infrastructure” and six nested hooks before you even see a single <div>.
Abstraction is like salt in a soup. You absolutely need it to make the thing work, but the second you overdo it, the entire meal is ruined.
The Tradeoff: Readability vs. “Cleverness”
The biggest lie we tell ourselves in React development is that DRY (Don't Repeat Yourself) is the ultimate goal. It isn't. Clarity is the ultimate goal.
Every time you abstract logic into a custom hook, you introduce indirection. Indirection is the cognitive distance a developer has to travel to understand what the code is doing. If I have to open five different files to understand how a single form submits, that abstraction hasn't saved me time — it's stolen my focus.
The best code isn't the most “clever” code. It's the code that's easiest to delete. If your logic is “close to the ground” — inside the component — it's easy to reason about and even easier to refactor later.
When NOT to Use a Custom Hook
Hooks are the primary engine of over-abstraction today. Here is my personal checklist for when to keep that logic right where it is.
The “One-Off” Rule
If you're only using that logic in one component, leave it there. Don't create a useProfileData hook if only the Profile component ever needs it. You aren't “cleaning up” the component — you're just hiding its guts.
The Five-Line Vanity Refactor
If your hook is just five lines of useState and an onChange handler, it doesn't need to be a hook. Moving it to another file just to make your component look “slim” makes the next developer hunt for the source of truth.
The Thin Wrapper
Avoid hooks like useFetchUser(id) that just wrap a generic useQuery. It adds a layer of maintenance for almost zero functional gain.
Real Examples Where Hooks Made Things Worse
The “God Hook”
The most common trap. I once worked on a project with a hook called useDashboardState. It handled authentication, data fetching, sidebar toggles, and search filtering — all in one. Because everything was bundled, any tiny change to the search bar caused the entire dashboard — including the heavy data tables — to re-render. It was a performance nightmare born from the desire to be “organized.”
The “Prop-Drilling Hook”
This is a hook that requires 10 optional configuration parameters to handle every possible edge case. By the time you've configured the hook, you've written more code than if you had just written the logic from scratch inside the component.
Final Thoughts: Clarity over DRY
Don't be afraid of a little repetition. If writing the same three lines of code in two places makes those components 10x easier to read, do it.
As a senior, my goal isn't to write the fewest lines of code possible. It's to write code that a junior developer can understand at a glance — without needing a map and a flashlight. Keep it simple, keep it flat, and keep it close to the ground.