
The Foundation: Component Composition
Component composition is the cornerstone of React architecture. Instead of building monolithic components, we break functionality into smaller, reusable pieces that can be combined to create complex UIs.
Container and Presentational Components
One of the most fundamental patterns in React is the separation of container and presentational components:
jsx
// Container Component
const UserListContainer = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setLoading(false);
});
}, []);
return ;
};
// Presentational Component
const UserList = ({ users, loading }) => {
if (loading) return ;
return (
{users.map(user => (
))}
);
};
## Advanced Patterns for State Management
### Custom Hooks Pattern
Custom hooks allow us to extract component logic into reusable functions:
jsx
const useApi = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
### Provider Pattern
The Provider pattern is excellent for sharing state across multiple components without prop drilling:
jsx
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
{children}
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
## Performance Optimization Patterns
### Memoization with React.memo and useMemo
Preventing unnecessary re-renders is crucial for performance:
jsx
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
processed: heavyComputation(item)
}));
}, [data]);
return (
{processedData.map(item => (
))}
);
});
### Lazy Loading Pattern
Code splitting with React.lazy improves initial load times:
jsx
const LazyDashboard = lazy(() => import('./Dashboard'));
const LazySettings = lazy(() => import('./Settings'));
const App = () => {
return (
}>
} />
} />
);
};
## Error Handling Patterns
### Error Boundaries
Error boundaries catch JavaScript errors anywhere in the component tree:
jsx
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return ;
}
return this.props.children;
}
}
## Conclusion
These design patterns form the foundation of scalable React applications. By implementing these patterns consistently, you'll create codebases that are easier to maintain, test, and extend. Remember that patterns are tools – use them when they solve real problems, not just for the sake of using them.
The key to mastering React is understanding when and how to apply these patterns effectively. Start with the basics like component composition and custom hooks, then gradually incorporate more advanced patterns as your applications grow in complexity.
One of the most fundamental patterns in React is the separation of container and presentational components:
jsx
// Container Component
const UserListContainer = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setLoading(false);
});
}, []);
return ;
};
// Presentational Component
const UserList = ({ users, loading }) => {
if (loading) return ;
return (
{users.map(user => (
))}
);
};
## Advanced Patterns for State Management
### Custom Hooks Pattern
Custom hooks allow us to extract component logic into reusable functions:
jsx
const useApi = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
### Provider Pattern
The Provider pattern is excellent for sharing state across multiple components without prop drilling:
jsx
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
{children}
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
};
## Performance Optimization Patterns
### Memoization with React.memo and useMemo
Preventing unnecessary re-renders is crucial for performance:
jsx
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
processed: heavyComputation(item)
}));
}, [data]);
return (
{processedData.map(item => (
))}
);
});
### Lazy Loading Pattern
Code splitting with React.lazy improves initial load times:
jsx
const LazyDashboard = lazy(() => import('./Dashboard'));
const LazySettings = lazy(() => import('./Settings'));
const App = () => {
return (
}>
} />
} />
);
};
## Error Handling Patterns
### Error Boundaries
Error boundaries catch JavaScript errors anywhere in the component tree:
jsx
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return ;
}
return this.props.children;
}
}
## Conclusion
These design patterns form the foundation of scalable React applications. By implementing these patterns consistently, you'll create codebases that are easier to maintain, test, and extend. Remember that patterns are tools – use them when they solve real problems, not just for the sake of using them.
The key to mastering React is understanding when and how to apply these patterns effectively. Start with the basics like component composition and custom hooks, then gradually incorporate more advanced patterns as your applications grow in complexity.
Article Tags

Kobigan.K
Senior Software Engineer at OXZON AI with expertise in React, Node.js, and modern web technologies. Passionate about building scalable applications and sharing knowledge with the developer community. With over 5 years of experience, Kobigan.K has contributed to numerous open-source projects and speaks at tech conferences.