Skip to content
React rc components 4 min read

Organizing Components

A React app starts as a handful of components and quietly grows into hundreds. Without a deliberate structure, you end up scrolling through a flat components/ folder hunting for the right file, or worse, accidentally creating duplicates. Good organization is not about following one “correct” layout — it is about choosing conventions early and applying them consistently so any teammate can predict where a file lives.

One component per file

Keep each component in its own file, named after the component. A file should export a single primary component as a default or named export, with closely related helpers (a tiny presentational subcomponent, a formatting function) living alongside it only when they are never used elsewhere.

// UserCard.jsx
export default function UserCard({ user }) {
  return (
    <article className="user-card">
      <Avatar src={user.avatarUrl} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </article>
  );
}

// Local helper — only used by UserCard, so it stays here.
function Avatar({ src, alt }) {
  return <img className="avatar" src={src} alt={alt} loading="lazy" />;
}

The moment Avatar is reused by another component, promote it to its own file. This rule keeps files small, makes imports unambiguous, and lets your editor’s “go to file” jump straight to the component you mean.

Colocate styles, tests, and helpers

Put everything a component owns next to the component. When you delete the component, you delete the whole folder — no orphaned CSS or stale tests left behind. A self-contained component folder typically looks like this:

UserCard/
├── UserCard.jsx
├── UserCard.module.css
├── UserCard.test.jsx
└── index.js

The index.js barrel re-exports the component so consumers import from the folder, not the inner file:

// UserCard/index.js
export { default } from "./UserCard.jsx";
// Somewhere else in the app
import UserCard from "../components/UserCard";

Colocation beats clever categorization. If a stylesheet, a test, and a hook all serve one component, they belong in that component’s folder — not in global styles/, tests/, and hooks/ directories that force you to jump around the tree to understand one feature.

Feature-based vs type-based folders

There are two dominant ways to group the folders themselves. Type-based organization groups files by what they are; feature-based organization groups them by what they do.

# Type-based (good for small apps)        # Feature-based (scales better)
src/                                       src/
├── components/                            ├── features/
├── hooks/                                 │   ├── checkout/
├── pages/                                 │   │   ├── CheckoutForm.jsx
├── utils/                                 │   │   ├── useCart.js
└── api/                                   │   │   └── cart.api.js
                                           │   └── profile/
                                           ├── components/   # truly shared UI
                                           └── lib/          # truly shared utils
ConcernType-basedFeature-based
Best forSmall apps, prototypesMedium-to-large apps
Finding related codeSpread across foldersAll in one place
OnboardingFamiliar at first glanceMaps to product domains
Riskcomponents/ becomes a dumping groundDeciding what counts as “shared”

Start type-based when the app is tiny, and migrate to feature-based once a single change routinely touches files in three different top-level folders. Keep a components/ (or ui/) folder for genuinely shared, domain-agnostic pieces like buttons and modals.

Barrel files

A barrel file (index.js) collects exports from a folder into one import surface. They make imports tidy:

// features/checkout/index.js
export { default as CheckoutForm } from "./CheckoutForm.jsx";
export { useCart } from "./useCart.js";
import { CheckoutForm, useCart } from "../features/checkout";

Barrels are convenient but not free. A barrel that re-exports an entire folder can defeat tree-shaking and create circular imports if modules import from their own barrel. Use them at feature boundaries, keep them shallow, and avoid one giant src/index.js that re-exports the whole app.

Avoid deep barrel chains where barrels import other barrels. They are a common source of confusing circular-dependency errors and slower cold builds in Vite.

Naming conventions

Consistency matters more than the specific choice, but these conventions are the modern default:

ItemConventionExample
Component file/folderPascalCaseUserCard.jsx, UserCard/
Hook filecamelCase, use prefixuseCart.js
Non-component modulecamelCaseformatDate.js, cart.api.js
CSS ModuleName.module.cssUserCard.module.css
Test fileName.test.jsxUserCard.test.jsx
ConstantsUPPER_SNAKE inside filesMAX_ITEMS

Match the file name to the default export. A file named UserCard.jsx should export a component called UserCard, so search and stack traces stay readable.

Best Practices

  • Keep one component per file and name the file after the component’s default export.
  • Colocate a component’s styles, tests, and private hooks in its own folder.
  • Begin type-based; switch to feature-based once changes regularly span folders.
  • Reserve a shared components/ or ui/ folder for domain-agnostic, reusable pieces.
  • Use barrel files only at feature boundaries — never as deep, app-wide re-export chains.
  • Apply PascalCase to components and folders, camelCase with a use prefix to hooks.
  • Pick conventions early, write them down, and let a linter enforce them.
Last updated June 14, 2026
Was this helpful?