SOLID no React mudou de patamar em 2026. Por exemplo, a chegada do React 19 com Server Components nativos e o React Compiler em RC redefiniram o que significa “componente bem projetado”. Por outro lado, os princípios continuam exatamente os mesmos — mudou só a forma de aplicar. Portanto, neste guia atualizado você aprende SRP, OCP, LSP, ISP e DIP com exemplos práticos em TypeScript, contexto de Server Components, e quando NÃO aplicar SOLID pra evitar over-engineering.
O que é SOLID e por que ainda importa no React em 2026
SOLID é um acrônimo que representa cinco princípios fundamentais de design de software. Por exemplo, foram introduzidos por Robert C. Martin (Uncle Bob) na década de 2000. Embora originalmente pensados pra linguagens orientadas a objetos como Java, eles se aplicam perfeitamente ao React moderno — inclusive ao React 19.
Resumo dos 5 princípios em 2 minutos
- S — Single Responsibility (SRP): cada componente tem UMA razão pra mudar
- O — Open/Closed (OCP): aberto pra extensão, fechado pra modificação
- L — Liskov Substitution (LSP): componentes substituíveis sem quebrar a UI
- I — Interface Segregation (ISP): props enxutas e focadas
- D — Dependency Inversion (DIP): dependa de abstrações, não implementações
SOLID nasceu pra OOP — por que aplicar em componentes funcionais?
Em primeiro lugar, SOLID não é sobre classes — é sobre responsabilidades. Por isso, vale tanto pra class Component quanto pra function Component. Em outras palavras, o React virou funcional, mas as dores que SOLID resolve (acoplamento, dificuldade de testar, regressões em refactor) continuam idênticas. Além disso, custom hooks, render props, children e composição são as ferramentas idiomáticas de React pra realizar cada princípio SOLID — você já usa SOLID mesmo sem nomear.
SOLID também é a base teórica de Clean Architecture e Domain-Driven Design — ambos relevantes em React quando você tem múltiplas camadas (UI, hooks de domínio, serviços).
Single Responsibility Principle (SRP) no React
O Single Responsibility Principle estabelece que cada componente deve ter apenas uma razão pra mudar. Por exemplo, um componente que mistura lógica de negócio, manipulação de estado, chamadas de API e renderização visual viola o SRP. Dessa forma, manutenção fica difícil e bugs se multiplicam.
Refatorando um componente que faz tudo
// ❌ Violando SRP — componente faz tudo
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data))
.finally(() => setLoading(false));
}, []);
const formatDate = (date) => {
return new Date(date).toLocaleDateString('pt-BR');
};
if (loading) return <div>Carregando...</div>;
return (
<div className="profile">
<img src={user.avatar} />
<h1>{user.name}</h1>
<p>Membro desde {formatDate(user.createdAt)}</p>
</div>
);
}Custom hooks como ferramenta de SRP
Por outro lado, dá pra separar responsabilidades em hooks customizados, utility e componente de apresentação. Em seguida, cada parte tem UMA razão pra mudar:
// ✅ Seguindo SRP — responsabilidades separadas
// Hook customizado cuida da lógica de dados
function useUserData() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data))
.finally(() => setLoading(false));
}, []);
return { user, loading };
}
// Utility cuida da formatação
const formatDate = (date) => {
return new Date(date).toLocaleDateString('pt-BR');
};
// Componente cuida apenas da apresentação
function UserProfile() {
const { user, loading } = useUserData();
if (loading) return <LoadingSpinner />;
return (
<div className="profile">
<UserAvatar src={user.avatar} />
<UserInfo name={user.name} memberSince={formatDate(user.createdAt)} />
</div>
);
}Server Components como SRP nativo no React 19
Em 2026, React Server Components (RSC) materializaram SRP na própria infraestrutura. Por exemplo, você consegue separar fisicamente “componente que busca dado no banco” de “componente que renderiza UI interativa”:
// app/profile/page.tsx — Server Component
// SRP nativo: SÓ busca dado, roda no servidor
async function ProfilePage() {
const user = await db.user.findUnique({ where: { id: 1 } });
return <UserProfileUI user={user} />;
}
// app/profile/UserProfileUI.tsx — Client Component
// SRP nativo: SÓ renderiza UI interativa
'use client';
function UserProfileUI({ user }: { user: User }) {
const [editing, setEditing] = useState(false);
return editing ? <EditForm user={user} /> : <ViewMode user={user} />;
}Em outras palavras, Server Components são o SRP que React tentou ensinar há 8 anos — separação de responsabilidades materializada na infraestrutura. Por isso, em vez de “componente que busca + renderiza”, você tem dois componentes em dois ambientes (server e client) com responsabilidades distintas por design.
Open/Closed Principle (OCP) no React
O Open/Closed Principle afirma que componentes devem estar abertos pra extensão, mas fechados pra modificação. Por exemplo, você adiciona novas funcionalidades sem alterar o código existente. Em React, isso costuma ser implementado via props de renderização e composição.
Render props, children e slots
// ❌ Violando OCP — precisa modificar pra cada variação
function Button({ type, children }: { type: string; children: React.ReactNode }) {
if (type === 'primary') {
return <button className="btn-primary">{children}</button>;
}
if (type === 'danger') {
return <button className="btn-danger">{children}</button>;
}
// Cada novo tipo exige modificar essa função
return <button>{children}</button>;
}
// ✅ Seguindo OCP — extensível via props e composição
type ButtonProps = {
variant?: 'primary' | 'danger' | 'ghost';
size?: 'sm' | 'md' | 'lg';
icon?: React.ReactNode;
children: React.ReactNode;
};
function Button({ variant = 'primary', size = 'md', icon, children }: ButtonProps) {
return (
<button className={`btn btn-${variant} btn-${size}`}>
{icon && <span className="btn-icon">{icon}</span>}
{children}
</button>
);
}
// Extensão sem modificar: novos componentes que usam Button como base
function IconButton(props: ButtonProps) {
return <Button {...props} className="icon-only" />;
}Composição: a forma idiomática de OCP no React
Por outro lado, a forma mais idiomática de OCP no React é composição via children. Em outras palavras, em vez de passar props pra cada variação possível, você delega a customização ao consumidor:
// ✅ Composição pura — OCP perfeito
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>;
}
// Extensão infinita sem modificar Card:
function UserCard({ user }: { user: User }) {
return (
<Card>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</Card>
);
}Para padrões mais avançados como Factory Pattern em React, vale conhecer outras formas de criar componentes extensíveis sem precisar modificar a base.
Liskov Substitution Principle (LSP) no React
O Liskov Substitution Principle estabelece que componentes derivados devem ser substituíveis pelos seus componentes base sem quebrar a aplicação. Por exemplo, se você tem uma interface comum, qualquer implementação deve funcionar no lugar da outra. Em React, isso significa que componentes que aceitam props similares devem ser intercambiáveis.
// Interface comum para componentes de input
interface InputProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
disabled?: boolean;
}
// ✅ Seguindo LSP — todos podem ser substituídos
function TextInput({ value, onChange, placeholder, disabled }: InputProps) {
return (
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
disabled={disabled}
/>
);
}
function PasswordInput({ value, onChange, placeholder, disabled }: InputProps) {
return (
<input
type="password"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
disabled={disabled}
/>
);
}
// Função que aceita qualquer input que siga InputProps
function FormField({ Component, ...props }: { Component: React.FC<InputProps> } & InputProps) {
return <Component {...props} />;
}
// Uso intercambiável — segue LSP
<FormField Component={TextInput} value={name} onChange={setName} />
<FormField Component={PasswordInput} value={password} onChange={setPassword} />Como TypeScript te avisa antes de você quebrar o LSP
Além disso, TypeScript é seu aliado natural no LSP. Por exemplo, se você adicionar uma prop obrigatória num componente filho que não existe na interface base, o compilador reclama em tempo de build. Dessa forma, você não consegue substituir um componente por outro incompatível sem que o TypeScript te avise. Em outras palavras, LSP em React vira “responsabilidade do compilador” — você só precisa modelar a interface base com cuidado.
Interface Segregation Principle (ISP) no React
O Interface Segregation Principle defende que componentes não devem depender de interfaces que não usam. Por outro lado, é melhor ter várias interfaces específicas do que uma única interface gigante. Em React, isso significa props enxutas e focadas.
Quebrando interfaces grandes em props focadas
// ❌ Violando ISP — interface gigante
interface MegaUserProps {
// Dados básicos
name: string;
email: string;
avatar: string;
// Dados de perfil
bio: string;
location: string;
website: string;
// Configurações
notifications: boolean;
theme: string;
language: string;
// Permissões
isAdmin: boolean;
canEdit: boolean;
canDelete: boolean;
}
// Componente UserAvatar é forçado a receber MUITAS props que não usa
// ✅ Seguindo ISP — interfaces segregadas
interface BasicUser {
name: string;
email: string;
avatar: string;
}
interface UserProfile extends BasicUser {
bio: string;
location: string;
website: string;
}
interface UserSettings {
notifications: boolean;
theme: string;
language: string;
}
interface UserPermissions {
isAdmin: boolean;
canEdit: boolean;
canDelete: boolean;
}
function UserAvatar({ user }: { user: BasicUser }) {
return <img src={user.avatar} alt={user.name} />;
}
function UserSettingsPanel({ settings }: { settings: UserSettings }) {
return <div>{/* renderiza apenas configurações */}</div>;
}Em outras palavras, cada componente recebe APENAS o subset de dados que precisa. Por isso, refactor fica simples — alterações em “permissões” não afetam componentes que só usam “dados básicos”.
Dependency Inversion Principle (DIP) no React
O Dependency Inversion Principle sugere que módulos de alto nível não devem depender de módulos de baixo nível. Em vez disso, ambos devem depender de abstrações. Em React, isso significa que componentes devem depender de interfaces (abstrações), não de implementações concretas.
Custom hooks injetando fontes de dados
// ❌ Violando DIP — depende diretamente do fetch
function UserList() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// ✅ Seguindo DIP — depende da abstração useUsers
interface UserService {
fetchUsers: () => Promise<User[]>;
}
function useUsers(service: UserService) {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
service.fetchUsers()
.then(setUsers)
.finally(() => setLoading(false));
}, [service]);
return { users, loading };
}
// Implementações concretas (intercambiáveis)
const apiUserService: UserService = {
fetchUsers: () => fetch('https://api.example.com/users').then(r => r.json())
};
const mockUserService: UserService = {
fetchUsers: () => Promise.resolve([{ id: 1, name: 'Test' }])
};
// Componente desacoplado da fonte de dados
function UserList({ service = apiUserService }: { service?: UserService }) {
const { users, loading } = useUsers(service);
return loading ? <Spinner /> : (
<ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
);
}Como Server Actions e use() simplificam DIP em 2026
Em React 19, Server Actions e o hook use() reduzem ainda mais a fricção de DIP. Por exemplo, em vez de injetar serviços via props, você passa a Promise diretamente:
// React 19 — DIP com use() e Server Actions
import { use } from 'react';
async function fetchUsersFromAPI(): Promise<User[]> {
return fetch('/api/users').then(r => r.json());
}
function UserList({ usersPromise }: { usersPromise: Promise<User[]> }) {
const users = use(usersPromise); // desempacota a Promise reativamente
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// O componente pai decide qual fonte usar — desacoplado por design
function UsersPage() {
return <UserList usersPromise={fetchUsersFromAPI()} />;
}
Em outras palavras, use() elimina a necessidade de useEffect + estado para casos simples — você expressa “isto depende dessa Promise” e o React resolve.
Publicidade
SOLID com React Compiler: o que mudou em 2026
O React Compiler entrou em RC em 2026 e mudou o que significa “código performático em React”. Por exemplo, antes você precisava lembrar de envolver tudo em useMemo, useCallback e React.memo pra evitar re-renderizações. Em 2026, o Compiler faz isso automaticamente em tempo de build.
useMemo, useCallback e memo agora são opcionais
// Antes do React Compiler (e ainda válido sem Compiler)
const Button = React.memo(function Button({ onClick, label }) {
const handleClick = useCallback(() => onClick(), [onClick]);
const formattedLabel = useMemo(() => label.toUpperCase(), [label]);
return <button onClick={handleClick}>{formattedLabel}</button>;
});
// Com React Compiler — você só foca em SOLID
function Button({ onClick, label }: { onClick: () => void; label: string }) {
const handleClick = () => onClick();
const formattedLabel = label.toUpperCase();
return <button onClick={handleClick}>{formattedLabel}</button>;
}O que isso libera no design SOLID
Por outro lado, isso NÃO significa abandonar SOLID — significa o contrário. Em primeiro lugar, com a memoização automatizada, você gasta menos cognição em “performance manual” e mais em “design de responsabilidades”. Em outras palavras, o Compiler libera você pra focar em SRP, OCP, LSP, ISP e DIP em vez de cargo cult de memoização. Por isso, componentes bem desenhados ficam ainda mais valiosos — o Compiler otimiza por baixo, você desenha por cima.
Composition vs SOLID: rivais ou camadas?
Em discussões da comunidade React, surge frequentemente: “SOLID é exagero, React resolve tudo com composition”. Por outro lado, é uma falsa dicotomia. Em primeiro lugar, SOLID é a teoria que explica POR QUE seu código fica difícil de manter. Em segundo lugar, composição é a sintaxe idiomática de React que VIABILIZA aplicar SOLID. Em outras palavras, são camadas, não rivais.
Por exemplo, quando você usa children pra passar conteúdo customizado, está aplicando OCP sem nomear. Quando quebra um componente grande em pequenos compostos, está aplicando SRP sem nomear. Por fim, quando aceita uma prop renderItem em vez de hard-codar uma estrutura, está aplicando DIP sem nomear.
Quando composition já resolve sem nomear SOLID
Em projetos pequenos (~5 componentes), composition idiomática de React resolve sem precisar invocar SOLID explicitamente. Por outro lado, em projetos médios e grandes, vale conhecer SOLID por nome — porque dá vocabulário pra discutir trade-offs em code review. Em equipes, SOLID é a “linguagem comum” pra refatoração.
Vale também distinguir SOLID de vibe coding com IA — quando você gera código rapidamente com Claude ou Copilot, o risco de violar SOLID aumenta (a IA produz componentes que fazem 3 coisas porque você pediu 3 coisas). Por isso, conhecer SOLID protege a qualidade do código gerado por IA.
Quando NÃO aplicar SOLID em React
Em primeiro lugar, SOLID é guia, não dogma. Por outro lado, aplicar SOLID em todo componente de 20 linhas é over-engineering. Em outras palavras, conhecer SOLID inclui saber quando NÃO aplicar.
Sinais de over-engineering em componentes pequenos
Em segundo lugar, alguns sinais claros de que você está aplicando SOLID em excesso:
- Componente de 15 linhas com 4 custom hooks injetados. Por exemplo, em vez de simplificar, você criou 4 abstrações pra cobrir um caso simples. Em compensação, junte tudo no componente — refator depois se crescer.
- Interfaces TypeScript com mais comentários do que código. Em outras palavras, você modelou interfaces de domínio rico em uma feature que não justifica. Por isso, simplifique.
- 3 níveis de wrapper de composição pra renderizar 1 div. Dessa forma, você criou Card > CardContent > CardInner pra um componente que faz “renderiza texto”. Por outro lado, prefira inline.
- DIP injetado em componente que NUNCA vai mudar fonte. Por fim, se a fonte de dados é stable (ex: localStorage), não invente serviço injetável.
Por isso, a regra prática é: aplique SOLID quando o componente é reutilizável ou está em código que cresce. Em compensação, em features experimentais ou one-shots, vale priorizar velocidade.
Para aprofundar nos princípios SOLID e em design de software de qualidade, estes 3 livros do Robert C. Martin (Uncle Bob) — criador do SOLID — são leitura obrigatória em toda equipe sênior:
Conclusão
Em suma, dominar os princípios SOLID no React continua sendo fundamental em 2026 — mas a forma de aplicar evoluiu. Por exemplo, React 19 + Server Components materializaram SRP na infraestrutura. O React Compiler libera você de memoização manual pra focar em design. A composição é a sintaxe idiomática que viabiliza OCP, SRP e DIP sem precisar invocar os nomes acadêmicos.
Por outro lado, lembre que SOLID é ferramenta — não dogma. Em projetos pequenos, composição idiomática resolve. Em projetos médios e grandes, SOLID dá vocabulário comum pra equipe discutir trade-offs em code review. Em outras palavras, aplique com critério.
Para complementar, vale aprofundar em Clean Architecture (SOLID como infraestrutura) e em Agentic Engineering (manter qualidade SOLID em código gerado por IA). Em seguida, comece aplicando SRP e OCP em componentes complexos — você verá ganho imediato em testabilidade e manutenção.
Publicidade


























