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

gustavospriebe

Gustavo Priebe

Posted on May 28, 2023

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.

💖 💪 🙅 🚩
gustavospriebe
Gustavo Priebe

Posted on May 28, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related