Have you ever spent ages looking for the TV remote, only to find it on top of the fridge or buried under a pile of laundry—nowhere near the actual TV? You search every logical place, but it’s just not where it should be.
Working with poorly structured code feels the same. You waste time searching instead of actually getting things done.
A well-organized codebase isn’t just about keeping things tidy—it’s about making sure everything has its place, is easy to find, and serves its purpose efficiently. Nowhere is this more important than in how you structure your components.
One of the biggest decisions in organizing a React codebase is knowing when a component should remain local and when it should be made shared. Get this wrong, and you either end up with cluttered, redundant code or an overly abstracted mess that’s difficult to work with.
In this post, we’ll explore the difference between these two types of components, common pitfalls, and how to find the right balance.
Local vs. Shared Components - What are they
A local component is a subcomponent used only within a specific parent component or feature.
It is tightly coupled to that component and isn’t meant to be shared elsewhere.
# Local Structure --> Hierarchy
components/
│── ImageGrid/
│ ├── ImageGrid.tsx # Main Component
│ ├── Card.tsx # Local Component
│ ├── Button.tsx # Local Component
│
│── Form/
│ ├── Form.tsx # Main Component
│ ├── Button.tsx # Local Component
│
│── OtherComponent.tsx
A shared component, on the other hand, is designed to be used across multiple parent components.
It serves a more generalized purpose and isn’t tied to a single component.
# Shared Structure --> No Hierarchy
components/
│── ImageGrid.tsx
│── Card.tsx
│── Button.tsx
│── Form.tsx
│── OtherComponent.tsx
Let's Exemplify
Imagine you’re working on a project that includes an image grid and a form.
The image grid displays a collection of images, with each item represented as a card component, which itself contains a button.
ImageGrid
↳ Card
↳ Button
Meanwhile, the form contains fields and a button component that is identical to the one used in the image grid.
Form
↳ Button
Two different approaches could be used when structuring your components in a project:
- You could use a local approach, where every component would live inside its respective folder. The ImageGrid folder contains the ImageGrid, Card, and Button components, while the Form folder contains the Form and Button components.
Or
- You could use a shared component approach, where all components are placed at the root level. This eliminates duplication, since the Button component is now shared.
Both methods seem reasonable. But as the project grows, problems start to appear. Either strategy introduces issues, as we are about to see.
The Problem With a Flat Structure (Shared Approach)
Placing all components in a single folder may seem simple and straighforward. However, as your codebase grows, it quickly becomes unmanageable.
Subcomponents get mixed with larger parent components. There’s no clear hierarchy, making it hard to tell which components belong to which features.
Searching for specific components becomes frustrating and time-consuming.
This is like throwing all your tools into a single box without any organization.
Sure, everything is in one place—but finding the exact tool you need becomes a headache.
The Problem With an Overly Strict Hierarchy
On the other hand, taking an extremely rigid, local approach also introduces issues.
You carefully place each component inside its respective folder,
ensuring modularity. But what happens when two components need the same button?
Instead of reusing a single component, two identical are created. Now:
- Code is duplicated unnecessarily.
- Updating the button’s design requires changing multiple instances.
- Searching for the "main" button becomes difficult in a large codebase.
At first, this structure feels intuitive, but as the project scales, it leads to inconsistencies and wasted effort—the very thing a strict structure is meant to prevent.
How to Solve this Issue
Start with local components. Keep them scoped to the feature they belong to.
If you find yourself needing the same component elsewhere, promote it to a shared component only when necessary.
This approach:
✔ Keeps your codebase clean and structured.
✔ Avoids premature abstraction.
✔ Ensures reusability happens naturally as the project evolves.
Balancing local and shared components is crucial for maintainable React code.
By structuring your components thoughtfully, you can reduce complexity,
prevent duplication, and make your codebase easier to work with.
Neither a purely local nor a fully shared approach works perfectly on its own.
A well-structured codebase strikes a balance—keeping components scoped to
their parent components while promoting reusability only when necessary.
How do you structure your react projects?
Let's discuss it in the comments!