useContext
A primeira coisa que aprendi sobre a Context API do React foi sua promessa de simplicidade para fugir do “prop drilling”. E de fato, é uma solução limpa: você cria um Provider
, joga um valor nele e qualquer componente filho acessa esse valor com o hook useContext
. Fácil.
O que me confundiu no início, ou melhor, o que eu não tinha percebido, era o comportamento dele nas atualizações. Ao estudar mais a fundo, entendi a regra: quando o valor de um Provider
muda, todos os componentes que consomem aquele contexto com useContext
são re-renderizados. Sem exceção.
Para visualizar isso, o cenário clássico que imaginei foi um contexto com dados de usuário e tema:
// Um contexto hipotético
const AppContext = React.createContext();
function AppProvider({ children }) {
const [user, setUser] = useState({ name: "Bia" });
const [theme, setTheme] = useState("dark");
const value = { user, theme, setTheme, setUser };
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}
Agora, vamos pensar em dois componentes. Um só quer saber o nome do usuário. O outro só quer mudar o tema.
function UserWidget() {
const { user } = useContext(AppContext);
// Este componente só se importa com o 'user'.
return <p>Usuário: {user.name}</p>;
}
function ThemeSwitch() {
const { theme, setTheme } = useContext(AppContext);
// Este componente só se importa com o 'theme'.
return (
<button onClick={() => setTheme((t) => (t === "dark" ? "light" : "dark"))}>
Mudar Tema
</button>
);
}
Pela forma como o useContext
funciona, se o botão ThemeSwitch
for acionado, o estado theme
muda. Isso cria um novo objeto value
no Provider
. O resultado? O React mandaria o UserWidget
re-renderizar, mesmo que a informação user
não tenha mudado absolutamente nada. Em uma aplicação pequena, isso é irrelevante. Mas minha conclusão, depois de ler sobre isso, é que em projetos maiores, isso pode virar um gargalo de performance silencioso.
useContextSelector
Minha pesquisa me levou à biblioteca use-context-selector
. A promessa dela é resolver exatamente esse problema. A ideia central é simples: em vez de pegar o contexto inteiro, você usa uma função “seletora” para dizer qual pedacinho do estado aquele componente específico precisa.
A re-renderização só acontece se o valor retornado por essa função mudar.
Um detalhe crucial que a documentação da biblioteca aponta é a necessidade de usar o createContext
que vem com ela, e não o do React.
// A mudança começa aqui, usando o createContext da biblioteca
import { createContext, useContextSelector } from "use-context-selector";
const AppContext = createContext(); // O Provider continua igual.
// E os componentes mudam a forma que consomem os dados:
function UserWidget() {
// O seletor "escuta" apenas o state.user.name
const userName = useContextSelector(AppContext, (state) => state.user.name);
return <p>Usuário: {userName}</p>;
}
function ThemeSwitch() {
const theme = useContextSelector(AppContext, (state) => state.theme);
const setTheme = useContextSelector(AppContext, (state) => state.setTheme);
return (
<button onClick={() => setTheme((t) => (t === "dark" ? "light" : "dark"))}>
Mudar Tema
</button>
);
}
Com essa abordagem, a teoria diz que uma mudança no theme
ativaria apenas a re-renderização do ThemeSwitch
. O UserWidget
ficaria intacto, pois a fatia de estado que ele observa (state.user.name
) não teria sofrido alteração.
Conclusão Depois de Pesquisar
Com base no que estudei, montei um guia mental bem simples para ajudar decidir quando cada um se encaixa melhor.
-
useContext
(o padrão): É a primeira escolha para estados que raramente mudam. Tema (claro/escuro), idioma, status de login. Coisas que, uma vez definidas, ficam estáveis. Para esses casos, a simplicidade dele é imbatível. -
useContextSelector
: Passou a ser uma opção para contextos com “muita coisa acontecendo”. Um estado global com dados do usuário, notificações, status de loading, tudo junto. Se várias partes desse estado mudam de forma independente, ouseContextSelector
parece ser a ferramenta certa para evitar a cascata de re-renders.