DEV Community

Kartik Sharma
Kartik Sharma

Posted on

Stop the Chaos: My Go-To React Folder Structure Pattern

React.js is an incredible library for building web applications — from small prototypes to large-scale, production-grade apps. But unlike full-fledged frameworks, React doesn’t enforce a structure. This flexibility is powerful, yet it comes with a trade-off: you’re responsible for designing a maintainable architecture.

Technically, React won’t stop you from putting your entire app in a single .tsx file (please don’t do this), but such freedom often leads to messy codebases that are hard to scale and hand over. Poor structure can quickly become a blocker in scenarios like:

1. Handover to Other Developers

When another developer joins the project, they might have no idea where things live — where’s the business logic? where’s the UI code? Without a clear separation (like containers vs display), reusing or refactoring components becomes risky; one wrong move could break the entire app.

2. Confusion About Existing Code

Without organization, it’s tough to know what already exists. Are there utility functions for this? Do we already have a custom hook for that? Developers end up reinventing the wheel or combing through files line by line to find what they need.

3. Painful File Navigation

If naming conventions or folder patterns aren’t standardized, simply navigating the codebase becomes frustrating. You waste time searching for related files instead of building features.

Solving These Issues with the Display + Container Pattern

To overcome the challenges mentioned above, I prefer using the Display + Container Pattern in our React projects. Over time, this approach has massively improved our team’s productivity — we spend less time untangling code and more time shipping features.

The beauty of this pattern is that it’s not something you need to memorize like syntax rules; it’s more like a simple formula (2 + 2 = 4). Once you apply it a few times, it quickly becomes second nature to organize your components this way.

Let me walk you through a practical example so you can see exactly how it works. Let’s have a look at this Feature/Section I have in one of my project where I am simply Rendering the User’s Profile & there Details

Folder Structure

src/
├── features/
   └── UserProfile/
       ├── index.tsx                # Export container (entry point)
       ├── UserProfileContainer.tsx # Logic + state mgmt
       ├── UserProfileDisplay.tsx   # Pure UI
       ├── hooks/                   # Hooks specific to UserProfile
          └── useUserProfile.ts
       └── components/              # Sub components specific to display
           └── UserAvatar.tsx
Enter fullscreen mode Exit fullscreen mode

Let’s Break down Each File & There purpose One by one!

Barrel Export

index.tsx

export { default } from './UserProfileContainer';
Enter fullscreen mode Exit fullscreen mode

Whenever I need to use the UserProfile section across my app, I won’t import it directly from UserProfileContainer.tsx.

Instead, I’ll import it via the index.tsx file using Barrel Exports!

This approach standardizes a single entry point for the component/feature, making imports cleaner, easier to manage, and more scalable as the project grows.

Usage Example:

import UserProfile from '@/features/UserProfile'; // imports container by default

Enter fullscreen mode Exit fullscreen mode

Container Component

UserProfileContainer.tsx

import React from 'react';
import UserProfileDisplay from './UserProfileDisplay';
import { useUserProfile } from './hooks/useUserProfile';

interface Props {
  userId: string;
}

const UserProfileContainer: React.FC<Props> = ({ userId }) => {
  const { user, loading, handleFollow } = useUserProfile(userId);

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>User not found</p>;

  return <UserProfileDisplay user={user} onFollow={handleFollow} />;
};

export default UserProfileContainer;

Enter fullscreen mode Exit fullscreen mode

The Container Component is responsible for rendering the Display Component and handling all business logic.

This includes tasks such as data fetching, form submission, and processing or preparing relevant data before passing it to the Display Component as props.

In short, the Container manages logic and state, while the Display focuses solely on UI rendering.

Display Component

UserProfileDisplay.tsx

import React from 'react';
import { User } from '@/types/user.types';
import UserAvatar from './components/UserAvatar';

interface Props {
  user: User;
  onFollow: () => void;
}

const UserProfileDisplay: React.FC<Props> = ({ user, onFollow }) => {
  return (
    <div className="p-4 border rounded shadow-sm">
      <UserAvatar name={user.name} />
      <h2 className="text-xl font-bold">{user.name}</h2>
      <p className="text-gray-600">{user.email}</p>
      <button
        className="mt-2 px-3 py-1 bg-blue-500 text-white rounded"
        onClick={onFollow}
      >
        Follow
      </button>
    </div>
  );
};

export default UserProfileDisplay;

Enter fullscreen mode Exit fullscreen mode

All the relevant data and event handlers are passed down as props to the Display Component.

This means its only responsibility is to render the data—nothing more, nothing less.

By keeping it purely presentational, the Display Component stays simple, reusable, and easy to test.

Feature-Specific Hooks

hooks/useUserProfile.ts

import { useEffect, useState } from 'react';
import { getUserProfile } from '@/services/userService';
import { User } from '@/types/user.types';

export const useUserProfile = (userId: string) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getUserProfile(userId).then((data) => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  const handleFollow = () => {
    alert(`Followed ${user?.name}!`);
  };

  return { user, loading, handleFollow };
};

Enter fullscreen mode Exit fullscreen mode

All the relevant custom hooks will be stored inside the dedicated hooks**/ directory** to keep the project structure organized and maintainable.

Nested Components

components/UserAvatar.tsx

import React from 'react';

interface Props {
  name: string;
}

const UserAvatar: React.FC<Props> = ({ name }) => {
  return (
    <div className="w-12 h-12 rounded-full bg-gray-300 flex items-center justify-center text-white font-bold">
      {name.charAt(0)}
    </div>
  );
};

export default UserAvatar;

Enter fullscreen mode Exit fullscreen mode

Naturally, if you’re building an entire page, it will likely contain nested components and smaller UI pieces.

These smaller components should be stored inside the `/components` directory for better organization.

Additionally, if a small component (like UserAvatar.tsx) doesn’t include any business logic or interactive behavior, you can store it directly in the directory—keeping it simple and purely presentational.

Real-World Scenario

Let’s imagine you’re working on the UserProfile page and need to make some changes.

With the Display + Container pattern in place, you instantly know where to look based on the type of change you’re making — no guesswork, no memorizing file names, just logical calculation.

Need to change API/state logic? → Go to UserProfileContainer.tsx → Else need UI/styling change? → Go to UserProfileDisplay.tsx → Nested UI? → Check components/ folder.

Step-by-Step Thought Process

  1. Identify your task → Is it logic-related or UI-related?
  2. Logic-related? → Go to Container file.
  3. UI-related? → Go to Display file.
  4. For nested UI pieces → Check components folder under the feature.
  5. For hooks used only in this feature → Check hooks folder under the feature.

This logical separation makes onboarding new developers and scaling features significantly easier.

Wrapping Up

That’s pretty much it for the file patterns and practices I prefer in my React projects.

I hope this guide serves as a helpful reference or even a hand‑off document for your team when structuring future projects.

And remember — this approach isn’t about memorizing rules; it’s about following a simple, predictable pattern that makes your codebase easier to navigate and maintain.

Top comments (2)

Collapse
 
derstruct profile image
Alex

Thanks for sharing.
The file organization topic is overlooked for some reason, despite being a fundamental part of any code base.
React is not my stack, but your structure sparks some ideas.

Collapse
 
difficultworld24 profile image
Kartik Sharma

Thanks Mate! Glad it helps!