SOLID Principles
The concept
Section titled “The concept”The SOLID principles are a set of five design principles intended to make software designs more understandable, flexible, and maintainable. Promoted by Robert C. Martin (Uncle Bob), they can form a core philosophy for methodologies such as agile software development.
SOLID is a mnemonic for five design principles intended to make software designs more understandable, flexible, and maintainable.
~ Wikipedia
The Principles
Section titled “The Principles”S - Single Responsibility Principle (SRP)
Section titled “S - Single Responsibility Principle (SRP)”A class (or function/component) should have only one reason to change.
In a React context, avoid “God Components” that handle data fetching, complex logic, and UI rendering all at once.
// ❌ Bad: Doing too muchfunction UserProfile() { const [user, setUser] = useState(null);
useEffect(() => { fetch('/api/user').then(r => r.json()).then(setUser); }, []);
const handleLogin = () => { /* ... */ };
if (!user) return <Spinner />;
return ( <div className="card"> <h1>{user.name}</h1> <button onClick={handleLogin}>Login</button> </div> );}
// ✅ Good: Separation of concerns// UserDataContainer handles fetching// UserCard handles UIfunction UserProfile() { const { user, loading } = useUser(); // Custom hook for logic
if (loading) return <Spinner />; return <UserCard user={user} />;}O - Open/Closed Principle (OCP)
Section titled “O - Open/Closed Principle (OCP)”Software entities should be open for extension, but closed for modification.
You should be able to add new functionality without changing existing code.
// ❌ Bad: Modifying the function for every new rolefunction getDashboardRoute(role: string) { if (role === 'admin') return '/admin'; if (role === 'manager') return '/manager'; // Need to edit this file to add 'user'... return '/';}
// ✅ Good: Configuration object (Extension)const roleRoutes: Record<string, string> = { admin: '/admin', manager: '/manager', user: '/dashboard',};
function getDashboardRoute(role: string) { return roleRoutes[role] || '/';}L - Liskov Substitution Principle (LSP)
Section titled “L - Liskov Substitution Principle (LSP)”Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.
If you have a generic Database class, swapping it for PostgresDatabase or
MongoDatabase shouldn’t crash your app if they implement the same interface.
interface Storage { save(data: string): void;}
class LocalStorage implements Storage { save(data: string) { localStorage.setItem('key', data); }}
class CloudStorage implements Storage { save(data: string) { // ❌ Violation if this throws an error the parent doesn't expect // or requires different parameters. // But if it adheres to the contract, it's LSP compliant. api.post('/save', { data }); }}I - Interface Segregation Principle (ISP)
Section titled “I - Interface Segregation Principle (ISP)”Clients should not be forced to depend upon interfaces that they do not use.
Don’t create massive interfaces. Break them down.
// ❌ Bad: Forcing props that aren't neededinterface User { id: string; name: string; email: string; stripeId: string; // Not needed for a simple avatar passwordHash: string;}
function UserAvatar({ user }: { user: User }) { return <img src={user.avatarUrl} />; // We only needed the URL!}
// ✅ Good: Pick what you needinterface AvatarProps { avatarUrl: string; name: string;}
function UserAvatar({ avatarUrl, name }: AvatarProps) { return <img src={avatarUrl} alt={name} />;}D - Dependency Inversion Principle (DIP)
Section titled “D - Dependency Inversion Principle (DIP)”Depend upon abstractions, not concretions.
High-level modules should not depend on low-level modules. Both should depend on abstractions.
In a functional world, this often means passing dependencies as arguments (Dependency Injection) or using Higher Order Functions.
// ❌ Bad: Hard dependency on a specific library (axios)const getUsers = () => { return axios.get('/users');}
// ✅ Good: Dependency Injection via Higher Order Functiontype HttpClient = (url: string) => Promise<unknown>;
const createGetUsers = (httpClient: HttpClient) => () => { return httpClient('/users');}
// Usageconst getUsers = createGetUsers(fetch);Resources
Section titled “Resources”- Clean Code by Robert C. Martin
- Uncle Bob on The Principles of OOD
- DigitalOcean on SOLID Principles in TypeScript