SOLID Principles in React
A Simple and Practical Guide
Introduction
Writing clean and maintainable code is crucial for React developers. The SOLID principles — originally designed for object-oriented programming — can be effectively applied to modern JavaScript frameworks like React to create robust, scalable software.
S — Single Responsibility Principle (SRP)
Definition: Each component should focus on one thing and have one reason to change.
SRP improves code quality by making components easier to understand, maintain, and test. When components have focused responsibilities, you can isolate issues without affecting other code.
Problem Example:
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(response => response.json())
.then(data => setUser(data));
}, []);
return <div>{user ? user.name : 'Loading...'}</div>;
}Solution: Separate data-fetching logic into a custom hook.
function useUserData() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(response => response.json())
.then(data => setUser(data));
}, []);
return user;
}
function UserProfile() {
const user = useUserData();
return <div>{user ? user.name : 'Loading...'}</div>;
}O — Open/Closed Principle (OCP)
Definition: Components should be open for extension but closed for modification.
This principle allows you to extend component behavior through props, higher-order components, or composition rather than editing original code.
Problem Example:
function Button({ label, color }) {
return <button style={{ backgroundColor: color }}>{label}</button>;
}Solution: Create specialized components without modifying the base.
function Button({ label, style }) {
return <button style={style}>{label}</button>;
}
function PrimaryButton(props) {
return <Button {...props} style={{ backgroundColor: 'blue' }} />;
}
function DangerButton(props) {
return <Button {...props} style={{ backgroundColor: 'red' }} />;
}L — Liskov Substitution Principle (LSP)
Definition: Subcomponents should be replaceable for their parent components without breaking functionality.
Specialized components should maintain consistent behavior while adding features.
function Button({ label, onClick, style }) {
return <button onClick={onClick} style={style}>{label}</button>;
}
function PrimaryButton(props) {
return <Button {...props} style={{ backgroundColor: 'blue', color: 'white' }} />;
}
function SecondaryButton(props) {
return <Button {...props} style={{ backgroundColor: 'grey', color: 'white' }} />;
}Both variants work identically to the base Button except for styling.
I — Interface Segregation Principle (ISP)
Definition: Components should only depend on the props and data they actually need.
This prevents creating large, bloated components with unused responsibilities.
Problem Example:
function Dashboard({ user, settings, notifications }) {
return (
<div>
<div>{user.name}</div>
<div>{settings.theme}</div>
<div>{notifications.length} notifications</div>
</div>
);
}Solution: Break into focused components.
function UserInfo({ user }) {
return <div>{user.name}</div>;
}
function UserSettings({ settings }) {
return <div>{settings.theme}</div>;
}
function UserNotifications({ notifications }) {
return <div>{notifications.length} notifications</div>;
}
function Dashboard({ user, settings, notifications }) {
return (
<div>
<UserInfo user={user} />
<UserSettings settings={settings} />
<UserNotifications notifications={notifications} />
</div>
);
}D — Dependency Inversion Principle (DIP)
Definition: High-level components should depend on abstractions rather than concrete implementations.
Components should rely on hooks, contexts, or services instead of directly coupling to specific implementations.
function ThemedButton({ themeService }) {
const theme = themeService.getTheme();
return <button style={{ backgroundColor: theme.background }}>Click me</button>;
}
// Usage
<ThemedButton themeService={lightThemeService} />
<ThemedButton themeService={darkThemeService} />This approach decouples components from specific implementations, improving testability and flexibility.
Conclusion
The five principles at a glance — SRP ensures components focus on one task, OCP enables extension without modification, LSP guarantees component substitutability, ISP promotes smaller focused interfaces, and DIP emphasizes abstract dependencies.
Applying these principles to your React projects leads to cleaner, more maintainable code.