Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    SOLID React

    SOLID translated into React habits — separate data from rendering, extend through composition, keep props minimal and depend on abstractions you pass in.

    Growingtended planted 1 min read
    On this page

    SOLID was written with classes in mind, and React mostly avoids classes. But the principles translate surprisingly well once you stop reading them literally. Here’s how I apply each one — only where it pays for itself.

    Single responsibility

    A component should have one reason to change. In practice that means separating data and logic from rendering: a custom hook fetches and shapes the data; the component just renders props. No more mega-components.

    const useWorkout = (id: string) =>
      useQuery({ queryKey: ["workout", id], queryFn: () => api.getWorkout(id) });
    
    const WorkoutCard = ({ id }: { id: string }) => {
      const { data } = useWorkout(id);
      return <Card title={data?.name} />;
    };

    Open/closed

    Extend behaviour without editing the component. In React that means composition — children, slots, render props — rather than inheritance or yet another boolean prop.

    <Card header={<Badge>PR</Badge>} footer={<ShareButton />} />

    The “boolean prop explosion” (<Button primary large danger outline>) is the classic open/closed smell. Use variants or composition instead.

    Liskov substitution

    A specialised component should honour the contract of the one it extends: same props, same behaviour, plus extras. An IconButton that silently ignores disabled breaks every caller that relied on Button.

    Interface segregation

    Pass what’s needed, not the whole object. <Avatar name={user.name} src={user.avatarUrl} /> beats <Avatar user={user} />. The component is easier to reuse, to test and to reason about, and it won’t re-render because some unrelated field on user changed.

    Dependency inversion

    Depend on abstractions you’re given rather than reaching out for concrete ones. Inject the API client through context or props, and tests can pass in a fake. This is the same idea as an anti-corruption layer at the data boundary — see API Modelling.

    Where I stop

    SOLID applied zealously produces a forest of tiny files and indirection nobody can follow. My rule: apply a principle when it removes a real pain — a component that’s hard to test, a change that touched ten files — not pre-emptively. These habits sit naturally inside the feature-based structure of Bulletproof React.

    • #react
    • #solid
    • #design-principles
    • #composition

    ↪ Links to (2)