No ecossistema React, é comum dividir uma aplicação em diversos componentes para torná-la mais modular e reutilizável. À medida que a hierarquia de componentes cresce, surge o desafio de passar dados e estados entre componentes que não estão diretamente aninhados, conhecido como "prop drilling". Isso pode levar a problemas de desempenho, complexidade no código e dificuldade na manutenção.
Neste artigo, discutiremos três formas eficientes de evitar o prop drilling no React: o uso do useContext, a composição de componentes e a biblioteca Zustand para gerenciamento de estado avançado.
Para demonstrar, utilizaremos como exemplo, um usuário que entra no sistema pela página de login e suas informações são armazenadas no estado login. Esse dados precisam ser consumidos apenas pelo componente de perfil que se encontra dentro da página de painel.
Uso de Props no React
// LoginPage.jsx
import { useState } from "react";
import PainelPage from './PainelPage'
export default function LoginPage() {
const [login, setLogin] = useState({username: 'admin123', password: '12345'});
// ...
return (
<>
<PainelPage login={login} />
</>
);
}
// PainelPage.jsx
import ProfilePage from './ProfilePage'
export default function PainelPage(props) {
// ...
return (
<>
<ProfilePage login={props.login} />
</>
);
}
// ProfilePage.jsx
export default function ProfilePage(props) {
return (
<div classname='profile'>
<p> Welcome {props.login.username} </p>
</div>
)
}
No código fornecido como exemplo, podemos observar o uso de props para transmitir as informações de login de um componente para outro. No entanto, quando um componente precisa acessar o estado não sendo um pai direto, ocorre o prop drilling, em que os dados são passados por vários componentes intermediários que não os utilizam diretamente. Isso resulta em um aumento desnecessário da quantidade de renderizações a cada atualização de estado e diminui a reusabilidade dos componentes.
Uso de useContext
O useContext é uma forma nativa do React de compartilhar dados entre componentes sem a necessidade de passar props manualmente em cada nível da hierarquia. É especialmente útil quando é necessário transmitir dados comuns a vários componentes que não mudam com frequência.
Para utilizar o useContext, primeiro, criamos um contexto que armazena o estado de login e permite acesso a esse estado em qualquer parte da aplicação. Podemos fazer isso da seguinte maneira:
// LoginContext.jsx
import { createContext, useState } from 'react';
export const LoginContext = createContext();
export const LoginProvider = ({ children }) => {
const [login, setLogin] = useState();
return (
<LoginContext.Provider value={{ login, setLogin }}>
{children}
</LoginContext.Provider>
);
};
No código acima, criamos um contexto chamado LoginContext
usando a função createContext()
do React. Em seguida, definimos um provedor (LoginProvider
) que envolve os componentes filhos (children
). O provedor fornece o valor do contexto, que inclui o estado de login
e a função setLogin
para atualizá-lo.
Para utilizar esse contexto na aplicação, precisamos envolver o componente raiz com o LoginProvider
:
import React from 'react';
import { LoginProvider } from './LoginContext';
const App = () => {
...
return (
<LoginProvider>
{/* Resto da sua aplicação */}
</LoginProvider>
);
};
export default App;
Agora, o contexto de login estará disponível em qualquer componente que esteja dentro do LoginProvider
. Podemos acessar o contexto usando o hook useContext
nos componentes que precisam do estado de login:
// LoginPage.jsx
import { useState, useContext } from 'react';
import { LoginContext } from './LoginContext';
export default function LoginPage() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const { setLogin } = useContext(LoginContext);
// ...
return (
<>
<div>
<form
onSubmit={(e) => {
e.preventDefault();
setLogin({ username, password });
}}
>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button>Login</button>
</form>
</div>
</>
);
}
No exemplo acima, utilizamos o useContext
para obter a função setLogin
do contexto de login. Quando o formulário de login é enviado, chamamos a função setLogin
com as informações de username e password.
Para acessar o estado de login em outro componente, como o ProfilePage
, podemos fazer o seguinte:
// ProfilePage.jsx
import { useContext } from 'react';
import { LoginContext } from './LoginContext';
export default function ProfilePage() {
const { login } = useContext(LoginContext);
// ...
return (
<div className="profile">
<p>Welcome {login.username}</p>
</div>
);
}
Dessa forma, o componente ProfilePage
tem acesso ao estado de login diretamente através do contexto, evitando o prop drilling.
Composição de Componentes
A composição de componentes é outra abordagem para evitar o prop drilling. Em vez de passar props diretamente de um componente para outro, os componentes são aninhados hierarquicamente para formar uma composição lógica.
Podemos utilizar a composição de componentes da seguinte maneira:
// LoginPage.jsx
import { useState } from 'react';
import PainelPage from './PainelPage';
import ProfilePage from './ProfilePage';
export default function LoginPage() {
const [login, setLogin] = useState({ username: 'admin123', password: '12345' });
// ...
return (
<PainelPage>
<ProfilePage login={login} />
</PainelPage>
);
}
No exemplo acima, no componente LoginPage
, o componente ProfilePage
é aninhado dentro do componente PainelPage
como um componente filho. Assim, o componente ProfilePage
recebe as propriedades de login diretamente como prop.
// PainelPage.jsx
export default function PainelPage({ children }) {
return (
<div>
{children}
</div>
);
}
No componente PainelPage
, utilizamos a prop especial children
para renderizar o conteúdo que é passado como componente filho. Dessa forma, o componente ProfilePage
é renderizado dentro do PainelPage
.
// ProfilePage.jsx
export default function ProfilePage({ login }) {
// ...
return (
<div className="profile">
<p>Welcome {login.username}</p>
</div>
);
}
No componente ProfilePage
, recebemos as propriedades de login diretamente como login
e as utilizamos normalmente para exibir as informações desejadas.
Dessa forma, as propriedades de login são passadas diretamente do componente LoginPage
para o ProfilePage
por meio da composição de componentes.
Zustand
Além das abordagens mencionadas anteriormente, outra opção para evitar o prop drilling no React é o uso da biblioteca Zustand. O Zustand é um gerenciador de estado leve que oferece uma maneira simples de compartilhar e atualizar estados entre componentes.
Com o ele, você pode criar um store global que armazena o estado e fornecer acesso a esse estado para os componentes que precisam dele. Os componentes podem se inscrever para receber atualizações automáticas sempre que o estado for alterado, eliminando a necessidade de passar props manualmente.
// store.js
import create from 'zustand';
const useStore = create((set) => ({
login: {},
setLogin: (newLogin) => set(() => ({ login: newLogin })),
}));
export default useStore;
No exemplo acima, criamos um store utilizando a função create
do Zustand. O store possui uma propriedade login
que é o estado inicial e uma função setLogin
que permite atualizar o estado.
Para utilizar o estado de login em um componente, podemos fazer o seguinte:
// LoginPage.jsx
import { useState } from 'react';
import useStore from './store';
export default function LoginPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const setLogin = useStore((state) => state.setLogin);
// ...
return (
<>
<div>
<form
onSubmit={(e) => {
e.preventDefault();
setLogin({ username, password });
}}
>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button>Login</button>
</form>
</div>
</>
);
}
Utilizamos o hook useStore
para obter a função setLogin
do store do Zustand. Quando o formulário de login é enviado, chamamos a função setLogin
com as informações de username e password.
Dentro do ProfilePage
, podemos acessar o estado de login atualizado da seguinte forma:
// ProfilePage.jsx
import useStore from './store';
export default function ProfilePage() {
const login = useStore((state) => state.login);
// ...
return (
<div className="profile">
<p>Welcome {login.username}</p>
</div>
);
}
Dessa forma, o componente ProfilePage
utiliza o hook useStore
para acessar o estado de login atualizado automaticamente pelo Zustand, evitando o prop drilling.
Conclusão
O prop drilling pode ser um desafio em aplicações React, mas existem diversas abordagens para evitá-lo. Neste artigo, exploramos três formas de evitar o prop drilling: o uso do useContext
, a composição de componentes e a biblioteca Zustand.
Utilizando o useContext
, é possível criar um contexto para compartilhar dados entre componentes sem precisar passar props manualmente em cada nível da hierarquia. A composição de componentes permite organizar a estrutura lógica dos componentes de forma hierárquica, evitando a necessidade de passar props desnecessárias. O Zustand é uma biblioteca leve que oferece uma maneira simples de compartilhar e atualizar estados entre componentes.
Cada abordagem tem suas vantagens e é importante considerar a natureza específica do projeto ao escolher a mais adequada. Com a aplicação dessas técnicas, é possível melhorar o desempenho, a reusabilidade e a manutenção do código React.
Top comments (5)
Obrigado por compartilhar, primo <3
Opa mais um que tenho que ler!
Ja sofri mto com prop drilling no react, vlw pelo artigo primo
Ótimo artigo! Parabéns!
Boa