DEV Community

Cover image for Evitando o Prop Drilling no React - Um Guia Prático
Gustavo Priebe
Gustavo Priebe

Posted on

Evitando o Prop Drilling no React - Um Guia Prático

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
phenriquesousa profile image
Pedro Henrique

Obrigado por compartilhar, primo <3

Collapse
 
zoldyzdk profile image
Hewerton Soares

Opa mais um que tenho que ler!

Collapse
 
cherryramatis profile image
Cherry Ramatis

Ja sofri mto com prop drilling no react, vlw pelo artigo primo

Collapse
 
artenlf profile image
Luís Felipe Arten

Ótimo artigo! Parabéns!

Collapse
 
renanvidal profile image
Renan Vidal Rodrigues

Boa