React.js Fundamentals

React Best Practices & Code Organization

18 min Lesson 38 of 40

Project Structure and Organization

A well-organized project structure is crucial for maintainability, scalability, and team collaboration. Let's explore industry-standard patterns for organizing React applications.

Recommended Folder Structure

src/ ├── assets/ # Static files (images, fonts, icons) │ ├── images/ │ ├── fonts/ │ └── icons/ ├── components/ # Reusable UI components │ ├── common/ # Shared components │ │ ├── Button/ │ │ │ ├── Button.jsx │ │ │ ├── Button.module.css │ │ │ ├── Button.test.jsx │ │ │ └── index.js │ │ ├── Input/ │ │ └── Modal/ │ └── layout/ # Layout components │ ├── Header/ │ ├── Footer/ │ └── Sidebar/ ├── features/ # Feature-based modules │ ├── auth/ │ │ ├── components/ │ │ │ ├── LoginForm.jsx │ │ │ └── RegisterForm.jsx │ │ ├── hooks/ │ │ │ └── useAuth.js │ │ ├── services/ │ │ │ └── authService.js │ │ ├── store/ │ │ │ └── authSlice.js │ │ └── index.js │ ├── products/ │ └── dashboard/ ├── hooks/ # Custom hooks │ ├── useLocalStorage.js │ ├── useDebounce.js │ └── useFetch.js ├── services/ # API and external services │ ├── api.js │ ├── axios.js │ └── websocket.js ├── store/ # Global state management │ ├── index.js │ ├── rootReducer.js │ └── middleware.js ├── styles/ # Global styles │ ├── variables.css │ ├── reset.css │ └── utilities.css ├── utils/ # Helper functions │ ├── formatters.js │ ├── validators.js │ └── constants.js ├── routes/ # Route configuration │ ├── index.jsx │ ├── ProtectedRoute.jsx │ └── routes.config.js ├── App.jsx ├── index.jsx └── config.js
Structure Benefits:
  • Feature-based: Related code grouped together (features/)
  • Scalability: Easy to add new features without clutter
  • Discoverability: Developers can find code quickly
  • Reusability: Common components clearly separated

Naming Conventions

Consistent naming makes code more readable and maintainable:

// Components - PascalCase function UserProfile() { } function ProductCard() { } const NavigationMenu = () => { }; // Files - Match component name UserProfile.jsx ProductCard.tsx NavigationMenu.js // Custom Hooks - camelCase with 'use' prefix function useAuth() { } function useFetchData() { } function useLocalStorage() { } // Utility Functions - camelCase function formatCurrency() { } function validateEmail() { } function debounce() { } // Constants - SCREAMING_SNAKE_CASE const API_BASE_URL = 'https://api.example.com'; const MAX_RETRIES = 3; const DEFAULT_TIMEOUT = 5000; // CSS Modules - camelCase import styles from './Button.module.css'; <button className={styles.primaryButton}> // Enums/Objects - PascalCase or UPPER_CASE const UserRole = { ADMIN: 'admin', USER: 'user', GUEST: 'guest' }; const API_ENDPOINTS = { USERS: '/api/users', PRODUCTS: '/api/products' };

Component Organization Patterns

Pattern 1: Component Folder Structure

Each component gets its own folder with related files:

Button/ ├── Button.jsx # Main component ├── Button.module.css # Component-specific styles ├── Button.test.jsx # Unit tests ├── Button.stories.jsx # Storybook stories ├── useButton.js # Component-specific hook └── index.js # Public API (barrel export) // index.js - Barrel export export { Button } from './Button'; export { default } from './Button'; // Usage - clean imports import Button from '@/components/Button'; import { Button } from '@/components/Button';
Pattern 2: Container/Presentational Split

Separate logic (containers) from UI (presentational):

// UserListContainer.jsx - Logic and data fetching import { useEffect, useState } from 'react'; import { UserList } from './UserList'; import { fetchUsers } from './services'; export function UserListContainer() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchUsers() .then(setUsers) .finally(() => setLoading(false)); }, []); const handleDelete = (id) => { setUsers(users.filter(u => u.id !== id)); }; return ( <UserList users={users} loading={loading} onDelete={handleDelete} /> ); } // UserList.jsx - Pure presentational export function UserList({ users, loading, onDelete }) { if (loading) return <Spinner />; return ( <ul> {users.map(user => ( <UserItem key={user.id} user={user} onDelete={onDelete} /> ))} </ul> ); }

Code Splitting Strategies

Split code to reduce initial bundle size and improve load times:

// 1. Route-based code splitting import { lazy, Suspense } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; const Home = lazy(() => import('./pages/Home')); const Dashboard = lazy(() => import('./pages/Dashboard')); const Profile = lazy(() => import('./pages/Profile')); function App() { return ( <BrowserRouter> <Suspense fallback={<PageLoader />}> <Routes> <Route path="/" element={<Home />} /> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> </Routes> </Suspense> </BrowserRouter> ); } // 2. Component-based code splitting const HeavyChart = lazy(() => import('./components/HeavyChart')); function Analytics() { return ( <div> <h1>Analytics</h1> <Suspense fallback={<ChartSkeleton />}> <HeavyChart data={chartData} /> </Suspense> </div> ); } // 3. Library code splitting const Markdown = lazy(() => import('react-markdown').then(module => ({ default: module.default })) );
Code Splitting Benefits:
  • Faster Initial Load: Users download only what they need
  • Better Caching: Unchanged routes stay cached
  • Progressive Enhancement: Core app loads first, features load on demand
  • Reduced Bandwidth: Important for mobile users

Barrel Exports for Clean Imports

Use index files to create clean, organized import paths:

// components/common/index.js - Barrel export export { Button } from './Button'; export { Input } from './Input'; export { Modal } from './Modal'; export { Card } from './Card'; export { Spinner } from './Spinner'; // Before - messy imports import { Button } from './components/common/Button/Button'; import { Input } from './components/common/Input/Input'; import { Modal } from './components/common/Modal/Modal'; // After - clean imports import { Button, Input, Modal } from '@/components/common'; // features/auth/index.js - Feature barrel export { LoginForm } from './components/LoginForm'; export { RegisterForm } from './components/RegisterForm'; export { useAuth } from './hooks/useAuth'; export { authService } from './services/authService'; // Usage import { LoginForm, useAuth } from '@/features/auth';
Barrel Export Caution: While barrel exports improve import readability, they can impact tree-shaking. Modern bundlers handle this well, but be aware:
  • Don't re-export everything in large libraries
  • Use named exports, not default exports in barrels
  • Test bundle size to ensure tree-shaking works

Path Aliases for Cleaner Imports

Configure path aliases to avoid relative path hell:

// jsconfig.json or tsconfig.json { "compilerOptions": { "baseUrl": "src", "paths": { "@/*": ["*"], "@components/*": ["components/*"], "@features/*": ["features/*"], "@hooks/*": ["hooks/*"], "@utils/*": ["utils/*"], "@services/*": ["services/*"], "@store/*": ["store/*"] } } } // Before - relative path nightmare import Button from '../../../../components/common/Button'; import { useAuth } from '../../../features/auth/hooks/useAuth'; // After - clean absolute imports import Button from '@/components/common/Button'; import { useAuth } from '@/features/auth/hooks/useAuth';

Environment-Specific Configuration

// config/index.js - Centralized configuration const config = { apiUrl: process.env.REACT_APP_API_URL || 'http://localhost:3000', apiTimeout: 10000, environment: process.env.NODE_ENV, features: { enableAnalytics: process.env.REACT_APP_ENABLE_ANALYTICS === 'true', enableNotifications: true, maxUploadSize: 5 * 1024 * 1024, // 5MB }, social: { facebookAppId: process.env.REACT_APP_FACEBOOK_APP_ID, googleClientId: process.env.REACT_APP_GOOGLE_CLIENT_ID, }, }; export default config; // Usage import config from '@/config'; if (config.features.enableAnalytics) { initAnalytics(); } fetch(`${config.apiUrl}/users`, { timeout: config.apiTimeout });

Component Composition Best Practices

// ❌ BAD - Props drilling nightmare function App() { const [user, setUser] = useState(null); return <Layout user={user} setUser={setUser}> <Dashboard user={user} setUser={setUser}> <Profile user={user} setUser={setUser} /> </Dashboard> </Layout>; } // ✅ GOOD - Context for shared state const UserContext = createContext(); function App() { const [user, setUser] = useState(null); return ( <UserContext.Provider value={{ user, setUser }}> <Layout> <Dashboard> <Profile /> </Dashboard> </Layout> </UserContext.Provider> ); } // ❌ BAD - Monolithic component function UserDashboard() { // 500 lines of code // Fetching, state, validation, UI, everything } // ✅ GOOD - Composed components function UserDashboard() { const { user } = useUser(); return ( <> <UserHeader user={user} /> <UserStats userId={user.id} /> <UserActivity userId={user.id} /> <UserSettings user={user} /> </> ); }

Documentation and Comments

/** * Renders a customizable button component with multiple variants. * * @param {Object} props - Component props * @param {string} props.variant - Button style variant ('primary', 'secondary', 'danger') * @param {string} props.size - Button size ('small', 'medium', 'large') * @param {boolean} props.disabled - Whether button is disabled * @param {boolean} props.loading - Shows loading spinner * @param {function} props.onClick - Click handler * @param {React.ReactNode} props.children - Button content * * @example * <Button variant="primary" size="large" onClick={handleSubmit}> * Submit Form * </Button> */ export function Button({ variant = 'primary', size = 'medium', disabled = false, loading = false, onClick, children }) { // Implementation } // PropTypes for runtime validation Button.propTypes = { variant: PropTypes.oneOf(['primary', 'secondary', 'danger']), size: PropTypes.oneOf(['small', 'medium', 'large']), disabled: PropTypes.bool, loading: PropTypes.bool, onClick: PropTypes.func.isRequired, children: PropTypes.node.isRequired };
Exercise 1: Refactor an existing React project:
  • Reorganize files using the recommended folder structure
  • Implement barrel exports for common components
  • Set up path aliases in your build configuration
  • Create a centralized config file for environment variables
Exercise 2: Implement code splitting:
  • Identify the 5 heaviest components in your app
  • Implement route-based code splitting for all pages
  • Add component-based splitting for heavy charts/editors
  • Measure bundle size before and after
  • Target 30% reduction in initial bundle size
Exercise 3: Create a component library:
  • Build 5-10 reusable components (Button, Input, Card, etc.)
  • Use consistent naming and folder structure
  • Add PropTypes or TypeScript types
  • Write JSDoc comments for each component
  • Create an index.js with barrel exports