Atualizado em 17 de setembro de 2024
Gerenciar dados em aplicações React pode ser um grande desafio – principalmente quando muitos componentes estão envolvidos no compartilhamento e atualização desses dados. No React, os componentes usam states para gerenciar propriedades internamente, geralmente sendo manipulados usando hooks do React, como useState
ou useEffect
. No entanto, as coisas podem ficar um pouco complicadas quando devemos passar essas propriedades entre diversos componentes.
Em projetos de grande escala, a gestão de estados tende a se tornar ainda mais complexa, o que leva muitos desenvolvedores a acreditar que é preciso utilizar bibliotecas externas. Porém, o React oferece uma solução nativa: a Context API. Essa API simplifica o processo, permitindo o compartilhamento de dados de forma eficiente, sem a necessidade de ferramentas adicionais.
O que é Context API e qual a sua utilidade?
Em primeiro lugar, Context é uma API incorporada no React 16.3 que ajuda os desenvolvedores a evitar a passagem de dados por meio de uma cadeia de props. Em vez disso, a solução torna possível passar dados de componentes pais para filhos aninhados diretamente – algo semelhante ao Redux, mas de forma mais leve e fácil.
Além de ser uma forma de criar variáveis globais que podem ser chamadas sob demanda, Context API é uma ótima alternativa para evitar prop drilling. Também é muito simples de usar: basta criar um contexto usando React.createContext()
, retornando um Consumer
e um Provider
. Entenda mais sobre cada um destes termos:
- Um Provider, fornece estados para seus componentes filhos, tornando-se o pai de todos os componentes que podem precisar desses estados;
- Já um Consumer é o componente que consome os estados.
Para obter uma compreensão real do conceito, o projeto abaixo exemplifica algumas das aplicações da Context API. Ele consiste em um contador de pontuação simples para duas equipes e um card de resultados, que mostra a pontuação total de cada equipe.
Nesta aplicação, precisamos gerenciar dois estados, um para cada uma das pontuações das equipes. Fora isso, as funções onClick
irão lidar com esses estados e retornar a soma dos pontos. Temos duas maneiras de operar esses estados: prop drilling e Context API. É importante conhecer ambas para, assim, ter uma noção real dos prós e contras e quando usar uma ou outra.
O que é prop drilling?
Com a estratégia de prop drilling, os estados necessários para os componentes são passados como propriedades pelos próprios componentes. Se você ainda está confuso, confira o funcionamento do processo no fluxograma abaixo:
Aqui, os estados são criados na página inicial e enviados, via props, para os componentes Counter
e Result
. Eles, por sua vez, enviam esses estados para outros componentes que se aprofundam cada vez mais em sua aplicação. Isso causa o efeito drill e cria componentes aninhados.
Confira como nosso componente Home
está estruturado, inicializando estados e renderizando os componentes filhos, Counter
e Result
:
export default function Home() {
const [teamA, setTeamA] = useState(0);
const [teamB, setTeamB] = useState(0);
return (
<Container>
<Box>
<Counter
teamA={teamA}
setTeamA={setTeamA}
teamB={teamB}
setTeamB={setTeamB}
/>
</Box>
<Result result={teamA + teamB} />
</Container>
);
}
Qual o problema do prop drilling?
Como mencionado anteriormente, a utilização de um contexto nos ajuda a evitar o prop drilling. Mas por que é ruim usar o prop drilling e quais problemas essa estratégia pode causar?
No nosso exemplo da aplicação, há poucos problemas em utilizar essa abordagem, já que nossa aplicação ainda é pequena e possui uma quantidade baixa de componentes e interações de dados entre eles. Mas, e se tivéssemos um sistema mais complexo e com mais componentes para administrar?
Certamente, este não seria apenas um sistema confuso, mas que também causaria uma dor de cabeça em quem precisasse realizar manutenções e futuras implementações.
Na verdade, nem precisaríamos de um sistema maior para enfrentar problemas: se você simplesmente quiser apenas renomear o estado prop com o qual está trabalhando, o prop drilling fará com que você vasculhe cada arquivo e renomeie cada chamada de estado individualmente.
Na verdade, nem precisaríamos de um sistema maior para enfrentar problemas: se você simplesmente quiser apenas renomear o estado com o qual está trabalhando, o prop drilling fará com que você tenha que vasculhar cada arquivo e renomear cada chamada de estado individualmente.
E, apesar de ser muito simples, a abordagem de prop drilling vai forçá-lo a passar dados do componente pai para níveis abaixo até chegar ao filho desejado, mesmo que você não precise dos dados nesses componentes de nível superior. Isso torna o desenvolvimento desnecessariamente complexo.
Como um dos pilares do time de Desenvolvimento de Software da BIX Tecnologia envolve justamente a entrega de códigos performáticos e de fácil manutenção até mesmo por terceiros, o prop drilling não tem vez por aqui. E, por isso, na maioria dos casos, a escolha do nosso time está na Context API.
Como usar Context API?
Nosso objetivo aqui é fornecer dados para a aplicação sempre que necessário. Portanto, em vez de passar os dados para o componente de nível superior, vamos fornecer os dados sob demanda apenas onde são necessários.
Assim, para implementarmos isso, vamos utilizar o Provider Pattern. Ele nada mais é do que um HOC (Componente de Ordem Superior) fornecido por um objeto de contexto. Esse provider receberá uma propriedade de valor que contém os dados que queremos transmitir aos componentes. Cada componente agrupado nesse provider terá acesso ao valor.
E como usar dados em cada componente?
Já que estamos no React, hooks são o caminho a seguir: importe o hook useContext
, que recebe o contexto que contém a propriedade value que criamos. Isso nos permitirá ler e gravar dados no contexto.
Ao fazer isso, os componentes que não estão usando os dados não terão que lidar com eles, e os dados serão chamados apenas quando e onde necessário, no escopo correto.
Exemplos práticos da abordagem
Vamos voltar ao exemplo da nossa aplicação do placar. Primeiro, construímos um App Provider. Ele vai envolver a raiz da nossa aplicação no componente App Provider.
Como você deve ter percebido, não é neste context que a Context API funciona de verdade, certo? Podemos, e às vezes devemos, ter mais de um contexto em nossas aplicações. Isso ajudará a criar uma solução modular, facilitando o desenvolvimento e a manutenção posteriores.
App
import AppProvider from "./contexts";
import Home from "./pages/Home";
function App() {
return (
<AppProvider>
<Home />
</AppProvider>
);
}
export default App;
AppProvider
import React from 'react'
import { ScoreProvider } from './ScoreContext'
const AppProvider = ({ children }) => (
<ScoreProvider>{children}</ScoreProvider>
)
export default AppProvider
Após isso, utilizando createContext()
, seremos capazes de criar o ScoreProvider
. Esse é o elemento-chave da abordagem Context API.
Aqui, vamos declarar todos os estados de que precisamos, aplicar handlers como funções e, claro, exportar tudo como um objeto para que possamos importar props individuais em cada componente.
ScoreProvider
import React, {
createContext,
useState,
} from 'react'
export const ScoreContext = createContext({})
const ScoreProvider = ({ children }) => {
const [teamA, setTeamA] = useState(0);
const [teamB, setTeamB] = useState(0);
function handleIncreaseTeamAScore(){
setTeamA((prevScore) => prevScore+1)
}
function handleIncreaseTeamBScore(){
setTeamB((prevScore) => prevScore+1)
}
function handleDecreaseTeamAScore(){
setTeamA((prevScore) => prevScore-1)
}
function handleDecreaseTeamBScore(){
setTeamB((prevScore) => prevScore-1)
}
function getScoreSum(){
return teamA+teamB
}
return (
<ScoreContext.Provider
value={{
teamA,
setTeamA,
teamB,
setTeamB,
handleIncreaseTeamAScore,
handleIncreaseTeamBScore,
handleDecreaseTeamAScore,
handleDecreaseTeamBScore,
getScoreSum
}}
>
{children}
</ScoreContext.Provider>
)
}
export { ScoreProvider }
Outra atitude inteligente a tomar nessa abordagem é criar um hook que faça a chamada do contexto por nós. Isso ajuda muito na construção de um código limpo e modular.
useScore hook
import { useContext } from "react"
import { ScoreContext } from "../contexts/ScoreContext"
function useScore() {
const context = useContext(ScoreContext)
if (!context) {
throw new Error('useScore must be used within a ScoreProvider')
}
return context
}
export { useScore }
Tudo isso é feito para que, no final, nossos componentes possam evoluir do que vimos anteriormente, para o exemplo abaixo. Observe como apenas chamamos o que precisamos em cada um deles.
Home Page
export default function Home() {
return (
<Container>
<Box>
<Counter />
</Box>
<Result />
</Container>
);
}
Resultados
export function Result() {
const { getScoreSum } = useScore();
return (
<Box>
<Paper>
<h1>Sum of score</h1>
<h2>{getScoreSum()}</h2>
</Paper>
</Box>
);
}
Contador
export function Counter() {
return (
<Box>
<TeamA />
<TeamB />
</Box>
);
}
Componente Equipe A
export function TeamA() {
const { teamA, handleIncreaseTeamAScore, handleDecreaseTeamAScore } =
useScore();
return (
<Paper>
<Box>
<h2>Team A</h2>
</Box>
<Scoreboard
score={teamA}
handleIncreaseScore={handleIncreaseTeamAScore}
handleDecreaseScore={handleDecreaseTeamAScore}
/>
</Paper>
);
}
Componente do Placar
export function Scoreboard({
score,
handleIncreaseScore,
handleDecreaseScore,
}) {
return (
<Paper>
<h2>Score Board</h2>
<Stack>
<Button onClick={handleIncreaseScore}>Score +</Button>
<Button onClick={handleDecreaseScore}>Score - </Button>
</Stack>
<h2>{score}</h2>
</Paper>
);
}
Quais conclusões tirar sobre a Context API?
Além da Context API do React, você deve considerar outras alternativas que podem se adequar melhor às necessidades do seu projeto, dependendo do porte da empresa, das relações de dependência e da complexidade envolvida. Ferramentas como Redux, Hookstate e Recoil também são populares, cada uma com suas particularidades. Para o time de Desenvolvimento de Software da BIX Tecnologia, a Context API funciona muito bem na maioria dos casos.
Com a Context API, é possível reduzir drasticamente o uso de props (prop drilling) e simplificar a comunicação entre os componentes. Depois que você começa a usá-la, é difícil imaginar voltar aos métodos anteriores. Experimente, aproveite todos os recursos e mantenha a mente aberta para outras soluções que possam complementar suas necessidades.
Se você está avaliando qual solução faz mais sentido para a arquitetura do seu projeto ou já utiliza a Context API e quer explorar novas possibilidades, o time da BIX Tecnologia pode ajudar. A BIX tem experiência apoiando equipes de desenvolvimento a superar desafios complexos. Por isso, tem insights e dicas especializadas para você! Marque uma conversa para discutir possíveis soluções para o seu desafio.