diogodev_
Conteudo

10 de março de 20264 minutos de leitura

SOLID aplicado em projetos React e Next.js

Os princípios SOLID são muito conhecidos no desenvolvimento orientado a objetos, mas também podem ser aplicados no desenvolvimento frontend moderno.

Em aplicações construídas com React e Next.js, seguir esses princípios ajuda a organizar melhor componentes, separar responsabilidades e manter o código mais fácil de escalar e manter.

Os princípios foram popularizados por Robert C. Martin e continuam sendo extremamente relevantes mesmo em arquiteturas modernas baseadas em componentes.

Neste artigo veremos como aplicar SOLID na prática em projetos React e Next.js.

Contexto

Conforme aplicações frontend crescem, é comum encontrar problemas como:

  • componentes muito grandes
  • lógica de negócio misturada com UI
  • dependências difíceis de trocar
  • código difícil de testar

Aplicar princípios de arquitetura como SOLID ajuda a evitar esses problemas, criando aplicações mais organizadas e sustentáveis.

S — Single Responsibility Principle no React

O Single Responsibility Principle diz que uma unidade de código deve ter apenas uma responsabilidade.

No React, isso significa evitar componentes que fazem muitas coisas ao mesmo tempo.

Exemplo ruim

Um componente que busca dados, renderiza UI e também trata lógica de negócio.

function UserProfile() {
const [user, setUser] = React.useState(null)

React.useEffect(() => {
fetch("/api/user")
.then((res) => res.json())
.then((data) => setUser(data))
}, [])

function updateUser() {
console.log("Atualizando usuário")
}

if (!user) {
return <p>Carregando...</p>
}

return (
<div>
<h1>{user.name}</h1>
<button onClick={updateUser}>Atualizar</button>
</div>
)
}

Esse componente possui múltiplas responsabilidades:

  • buscar dados
  • gerenciar estado
  • renderizar interface
  • executar ações

Exemplo aplicando SRP

Separando responsabilidades.

Hook responsável pelos dados:

function useUser() {
const [user, setUser] = React.useState(null)

React.useEffect(() => {
fetch("/api/user")
.then((res) => res.json())
.then(setUser)
}, [])

return user
}

Componente focado apenas na interface:

function UserProfile() {
const user = useUser()

if (!user) {
return <p>Carregando...</p>
}

return <UserView user={user} />
}

Agora cada parte do sistema possui uma responsabilidade clara.

O — Open/Closed Principle no React

O Open/Closed Principle diz que devemos poder estender comportamento sem modificar código existente.

Isso é muito comum usando component composition.

Exemplo ruim

function Button({ type }) {
if (type === "primary") {
return <button className="bg-blue-500">Primary</button>
}

if (type === "danger") {
return <button className="bg-red-500">Danger</button>
}
}

Toda vez que surge um novo tipo precisamos alterar o componente.

Exemplo aplicando OCP

function Button({ children, className }) {
return (
<button className={`px-4 py-2 ${className}`}>
{children}
</button>
)
}

Uso:

<Button className="bg-blue-500">Primary</Button>

<Button className="bg-red-500">Danger</Button>

Agora podemos estender comportamento sem modificar o componente.

L — Liskov Substitution Principle em componentes

O Liskov Substitution Principle diz que componentes derivados devem poder substituir os originais sem quebrar comportamento.

Exemplo ruim

function Button({ onClick }) {
return <button onClick={onClick}>Click</button>
}

function LinkButton() {
return <a href="/home">Home</a>
}

Se trocarmos Button por LinkButton, o comportamento muda completamente.

Exemplo melhor

function Button({ children, ...props }) {
return <button {...props}>{children}</button>
}

function LinkButton(props) {
return <Button as="a" {...props} />
}

Agora ambos seguem o mesmo contrato de uso.

I — Interface Segregation no frontend

No frontend, esse princípio significa não criar props gigantes que poucos componentes usam.

Exemplo ruim

function Card({ title, image, footer, sidebar, actions }) {
return (
<div>
<h2>{title}</h2>
<img src={image} />
{footer}
{sidebar}
{actions}
</div>
)
}

Esse componente tenta resolver muitos cenários diferentes.

Exemplo melhor

Criando componentes menores.

function Card({ children }) {
return <div className="card">{children}</div>
}

function CardHeader({ title }) {
return <h2>{title}</h2>
}

function CardFooter({ children }) {
return <footer>{children}</footer>
}

Agora cada componente possui uma responsabilidade clara.

D — Dependency Inversion em aplicações Next.js

Esse princípio ajuda a evitar dependência direta de implementações.

Exemplo ruim

async function getUsers() {
const response = await fetch("https://api.example.com/users")

return response.json()
}

Aqui o código depende diretamente da API.

Exemplo melhor

Criando uma abstração.

function createUserRepository(client) {
return {
async getUsers() {
return client.get("/users")
}
}
}

Uso:

const repository = createUserRepository(apiClient)

repository.getUsers()

Agora podemos trocar facilmente:

  • fetch
  • axios
  • mock para testes

Boas práticas ao aplicar SOLID em React

Algumas práticas ajudam a manter projetos React organizados:

  • manter componentes pequenos
  • separar hooks de lógica de negócio
  • usar composição de componentes
  • evitar props excessivas
  • separar UI de acesso a dados

Essas práticas tornam o código mais fácil de entender e evoluir.

Conclusão

Embora os princípios SOLID tenham surgido no contexto da programação orientada a objetos, eles continuam extremamente úteis em aplicações modernas com React e Next.js.

Aplicá-los no frontend ajuda a:

  • criar componentes mais reutilizáveis
  • reduzir complexidade
  • facilitar manutenção
  • melhorar testabilidade

Com o crescimento da aplicação, seguir esses princípios faz uma grande diferença na qualidade do código.