O que é a Context API?
A Context API é a forma nativa do React de passar dados através de uma árvore de componentes sem a necessidade de passar props por todos os níveis. Ela resolve um problema específico e comum: quando um valor é necessário para muitos componentes, mas está localizado muito acima deles na árvore.
O exemplo clássico é a tematização (theming). Um Button localizado no fundo da árvore precisa do tema atual, mas cada componente entre o topo e o botão teria que aceitar e encaminhar uma prop theme. Isso é chamado de prop drilling, e o Context elimina esse problema. Você fornece o valor apenas uma vez, e qualquer descendente pode lê-lo diretamente.
Criando e fornecendo contexto
O contexto é composto por três partes: um objeto, um provider e um hook de consumo.
// AuthContext.jsx
import { createContext, useContext, useMemo, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(
() => ({
user,
login: (nextUser) => setUser(nextUser),
logout: () => setUser(null),
}),
[user],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
createContext define o contexto e um valor padrão. O provider fornece o valor atual para tudo o que estiver abaixo dele. O hook customizado lê esse valor e retorna um erro claro caso o componente seja utilizado fora do provider.
Consumindo o contexto
Qualquer descendente pode ler o valor, não importa quão profundo esteja na árvore.
// Header.jsx
import { useAuth } from "./AuthContext";
export function Header() {
const { user, logout } = useAuth();
return (
<header>
<span>{user ? `Hi, ${user.name}` : "Guest"}</span>
{user && <button onClick={logout}>Sign out</button>}
</header>
);
}
O Header nunca recebeu uma prop do App. Ele buscou na árvore o valor de que precisava, o que mantém os componentes intermediários alheios aos dados que não utilizam.
Context não é estado
Essa distinção costuma confundir as pessoas. O Context não armazena estado nem gerencia atualizações. Ele transporta um valor. O estado em si reside em useState ou useReducer, e o provider o repassa para baixo.
É por isso que um padrão comum é combinar context com um reducer:
// CartContext.jsx
import { createContext, useContext, useReducer } from "react";
const CartContext = createContext(null);
function reducer(state, action) {
switch (action.type) {
case "add":
return [...state, action.item];
case "remove":
return state.filter((item) => item.id !== action.id);
default:
return state;
}
}
export function CartProvider({ children }) {
const [items, dispatch] = useReducer(reducer, []);
return (
<CartContext.Provider value={{ items, dispatch }}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => useContext(CartContext);
O reducer mantém as atualizações previsíveis, e o context as torna disponíveis em qualquer lugar sem a necessidade de prop drilling.
Performance: a real limitação
Quando o valor de um provider muda, todos os componentes que leem esse contexto são renderizados novamente. Não existe um selector, portanto você não pode se inscrever em apenas uma parte do valor. Dois hábitos tornam isso gerenciável.
Memorize o valor. Um objeto literal cria uma nova referência a cada renderização, o que significa que cada consumidor será renderizado novamente mesmo quando nada mudou.
// memoised
const value = useMemo(() => ({ user, logout }), [user]);
Divida os contextos. Coloque estados e ações em contextos separados, ou divida por funcionalidade, para que um componente que precisa apenas disparar uma ação não seja renderizado novamente quando o estado mudar.
// split.jsx
const CartStateContext = createContext(null);
const CartActionsContext = createContext(null);
Componentes que apenas chamam ações se inscrevem no contexto de ações, que nunca muda de identidade, portanto, eles nunca sofrem re-render devido a uma atualização de estado. Esse único truque resolve a maioria das reclamações de performance relacionadas a contextos.
Quando usar Context, e quando não usar
O Context é a ferramenta certa para:
- Temas e modo de cores.
- Usuário autenticado e sessão.
- Locale e traduções.
- Um router ou uma configuração pequena e estável.
Opte por um store dedicado quando você tiver:
- Atualizações frequentes, como digitação, arraste (dragging) ou animações.
- Uma árvore de estado grande onde os componentes precisam de selectors.
- Necessidade de middleware, persistência ou devtools de time-travel.
- Estado de servidor que precise de caching e refetching.
Bibliotecas como Zustand e Redux Toolkit adicionam selectors para que os componentes assinem fatias (slices) em vez do valor completo. Para dados de servidor, o TanStack Query gerencia o caching integralmente.
Melhores práticas
- Envolva
useContextem um custom hook e verifique se o provider está ausente. - Memorize os valores do provider com
useMemo. - Divida os contextos por responsabilidade, especialmente estado versus ações.
- Mantenha os valores do contexto pequenos e focados.
- Use
useReducerquando as atualizações forem complexas ou dependerem do estado anterior. - Não utilize contexto para atualizações de alta frequência; utilize um store baseado em seletores.
- Forneça valores padrão sensatos para que os componentes possam renderizar fora de um provider quando apropriado.
Erros comuns
- Tratar o context como um gerenciador de estado e esperar por selectors.
- Passar um objeto inline como valor do provider e causar a renderização de tudo novamente.
- Colocar dados não relacionados em um único context gigante.
- Esquecer o valor padrão e causar um crash em
undefined. - Usar context para valores que apenas um ou dois componentes precisam.
- Esperar que o context otimize as re-renders automaticamente.
Próximos passos
O Context é a base do compartilhamento de estado no React, e conhecer seus limites indica quando é hora de evoluir. Compare-o com o Redux Toolkit para estados previsíveis em larga escala e com o Zustand para um store de seletores leve; depois, gerencie dados do servidor com o TanStack Query. Para Vue, o conceito equivalente é abordado no guia do Pinia.