Evitando o Prop Drilling no React - Um Guia Prático
Tags: braziliandevs, programming, react, tutorial
Published: 2023-05-28 23:08:37


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

```jsx
// 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:

```jsx
// 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`:

```jsx
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:

```jsx
// 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:

```jsx
// 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:

```jsx
// 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.

```jsx
// 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`.

```jsx
// 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.

```jsx
// 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:

```jsx
// 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:

```jsx
// 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.