Best Practices for Structuring React Applications

By Maulik Paghdal

10 Dec, 2024

Best Practices for Structuring React Applications

Introduction

Building a React application can become challenging as your project grows. Following a well-organized structure is key to maintaining code quality, scalability, and collaboration. Let’s explore some of the best practices for structuring React applications.

1. Use a Modular Folder Structure

Group related files together to keep your codebase modular and easier to navigate.

Example Structure:

src/
├── components/       // Reusable components
├── pages/            // Page-level components
├── hooks/            // Custom hooks
├── contexts/         // Context API logic
├── services/         // API calls and service functions
├── utils/            // Utility functions and helpers
├── styles/           // Global and shared styles
├── App.js            // Root component
├── index.js          // Entry point

Why:

  • A modular structure ensures logical grouping of files.
  • It simplifies collaboration in larger teams by making the structure predictable.

2. Name Components and Files Consistently

Use PascalCase for component names and their file names. For non-component files, use camelCase or kebab-case based on your team’s convention.

Example:

  • Component file: MyComponent.js
  • Hook file: useCustomHook.js
  • Utility file: string-utils.js

Why:
Consistent naming makes it easier to locate files and understand their purpose at a glance.

3. Separate Business Logic from UI Logic

Keep your components focused on rendering UI and move business logic to custom hooks or helper functions.

Example:

// hooks/useFetchData.js
import { useState, useEffect } from "react";

export const useFetchData = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
};

// components/DataDisplay.js
import { useFetchData } from "../hooks/useFetchData";

const DataDisplay = () => {
  const { data, loading } = useFetchData("/api/data");

  if (loading) return <p>Loading...</p>;
  return <div>{JSON.stringify(data)}</div>;
};

export default DataDisplay;

Why:
Separating concerns improves reusability, testing, and readability of components.

For each component, include its related CSS/SCSS file or use CSS-in-JS libraries for encapsulated styles.

Example with CSS Modules:

components/
├── Button/
   ├── Button.js
   ├── Button.module.css

Why:

  • Encapsulation ensures that styles don’t unintentionally leak into other components.
  • It improves maintainability when components are self-contained.

5. Use Absolute Imports

Avoid relative import chains like ../../../../components/Button.js. Instead, configure absolute imports in your project.

Steps (using a jsconfig.json or tsconfig.json):

{
  "compilerOptions": {
    "baseUrl": "src"
  }
}

Why:
Absolute imports make code cleaner and easier to refactor.

6. Manage State Efficiently

For local component state, use useState or useReducer. For shared state, prefer Context API or libraries like Redux or Zustand.

Example:

import { createContext, useContext, useState } from "react";

// contexts/ThemeContext.js
const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);

// App.js
import { ThemeProvider, useTheme } from "./contexts/ThemeContext";

const App = () => (
  <ThemeProvider>
    <ThemedComponent />
  </ThemeProvider>
);

const ThemedComponent = () => {
  const { theme, setTheme } = useTheme();
  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={() => setTheme("dark")}>Toggle Theme</button>
    </div>
  );
};

Why:
Proper state management ensures your app is predictable and easy to debug.

7. Document Your Codebase

Add comments, README files, and inline documentation to explain the purpose of components and functions.

Why:
Documentation ensures that new developers can onboard quickly and understand your codebase.

Conclusion

A well-structured React application is key to building scalable, maintainable, and collaborative projects. By following these best practices, you can create a codebase that grows with your application while remaining clean and organized.

Focus on modularity, clear naming conventions, and separating concerns to streamline your development process.