React.js Fundamentals
React Best Practices & Code Organization
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