<h2>O Problema Real do useContext em Aplicações Escaláveis</h2>
<p>Quando iniciamos com React e descobrimos o <code>useContext</code>, parece uma solução mágica para evitar prop drilling. No entanto, conforme sua aplicação cresce, você descobrirá um problema fundamental: o <code>useContext</code> causa re-renders em <strong>todos os componentes que o consomem</strong>, independentemente de qual parte do estado eles realmente utilizam. Isso não é um bug; é o comportamento padrão do React, e ignorar essa realidade custará performance.</p>
<p>Imagine uma aplicação com um Context global contendo dados de usuário, tema, notificações e configurações. Se você tiver 50 componentes consumindo esse Context e apenas o tema mudar, todos esses 50 componentes sofrerão re-render, mesmo que 45 deles não usem a informação de tema. Essa cascata de re-renders desnecessários é exatamente o que exploraremos para resolver.</p>
<h2>Context Splitting: Dividindo para Vencer</h2>
<h3>O Conceito Fundamental</h3>
<p>Context Splitting é a prática de dividir um grande Context em múltiplos Contexts menores e especializados. Em vez de ter um <code>AppContext</code> monolítico, você cria <code>ThemeContext</code>, <code>UserContext</code>, <code>NotificationContext</code> etc. Cada componente então consome apenas os Contexts que realmente precisa, reduzindo drasticamente os re-renders.</p>
<p>A lógica é simples: se um componente só precisa do tema, deve estar conectado apenas ao <code>ThemeContext</code>. Quando o tema muda, somente os consumidores do <code>ThemeContext</code> sofrem re-render.</p>
<h3>Exemplo Prático: Antes do Splitting</h3>
<pre><code class="language-javascript">// ❌ Contexto monolítico - problema de performance
import React, { createContext, useState } from 'react';
export const AppContext = createContext();
export function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
const [settings, setSettings] = useState({});
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme, notifications, setNotifications, settings, setSettings }}>
{children}
</AppContext.Provider>
);
}</code></pre>
<p>Agora vamos ao componente que consome:</p>
<pre><code class="language-javascript">// Componente que só precisa do tema
function Header() {
const { theme, setTheme } = useContext(AppContext);
return (
<header style={{ background: theme === 'light' ? '#fff' : '#333' }}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</header>
);
}
// Componente que só precisa do usuário
function Profile() {
const { user } = useContext(AppContext);
return <div>User: {user?.name}</div>;
}</code></pre>
<p>Quando o tema muda no <code>Header</code>, o <code>Profile</code> também sofre re-render, <strong>mesmo sem usar</strong> a informação de tema. Se você tiver 20 componentes diferentes, são 20 re-renders desnecessários.</p>
<h3>Exemplo Prático: Depois do Splitting</h3>
<pre><code class="language-javascript"></code></pre>
<p>Agora a estrutura no App:</p>
<pre><code class="language-javascript">function App() {
return (
<ThemeProvider>
<UserProvider>
<NotificationProvider>
<Header />
<Profile />
<Sidebar />
</NotificationProvider>
</UserProvider>
</ThemeProvider>
);
}
// Header só sofre re-render quando tema muda
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<header style={{ background: theme === 'light' ? '#fff' : '#333' }}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</header>
);
}
// Profile só sofre re-render quando usuário muda
function Profile() {
const { user } = useContext(UserContext);
return <div>User: {user?.name}</div>;
}</code></pre>
<p>Agora, quando o tema muda, apenas <code>Header</code> sofre re-render. <code>Profile</code> e todos os componentes que consomem apenas <code>UserContext</code> ou <code>NotificationContext</code> <strong>permanecem intactos</strong>.</p>
<h2>Otimizando Performance com useMemo e useCallback</h2>
<h3>Entendendo o Problema de Referência</h3>
<p>Um erro comum é não memoizar o objeto <code>value</code> passado ao Provider. A cada render do Provider, um novo objeto é criado, causando re-render de todos os consumidores mesmo que os dados não tenham mudado.</p>
<pre><code class="language-javascript"></code></pre>
<h3>Callbacks e Performance</h3>
<p>Quando você expõe funções através do Context, também deve memoizá-las com <code>useCallback</code>:</p>
<pre><code class="language-javascript"></code></pre>
<h2>Separando Dados de Ações: O Padrão de Divisão Avançada</h2>
<h3>O Problema de Misturar Estado e Setters</h3>
<p>Quando você coloca dados e suas funções de atualização no mesmo Context, consumidores que só querem ler dados ainda sofrem re-render quando as ações mudam. A solução é criar dois Contexts: um para dados e outro para ações.</p>
<pre><code class="language-javascript">// Context para dados (apenas leitura)
export const UserDataContext = createContext();
// Context para ações (apenas escrita)
export const UserActionsContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
// Valores que mudam frequentemente
const dataValue = useMemo(
() => ({ user, loading }),
[user, loading]
);
// Ações que não mudam (memoizadas com useCallback)
const login = useCallback(async (email, password) => {
setLoading(true);
try {
// chamada à API
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const userData = await response.json();
setUser(userData);
} finally {
setLoading(false);
}
}, []);
const logout = useCallback(() => {
setUser(null);
}, []);
const actionsValue = useMemo(
() => ({ login, logout }),
[login, logout]
);
return (
<UserDataContext.Provider value={dataValue}>
<UserActionsContext.Provider value={actionsValue}>
{children}
</UserActionsContext.Provider>
</UserDataContext.Provider>
);
}</code></pre>
<p>Agora você pode criar hooks customizados para abstração:</p>
<pre><code class="language-javascript">// Hook para ler dados
export function useUserData() {
const context = useContext(UserDataContext);
if (!context) {
throw new Error('useUserData deve estar dentro de UserProvider');
}
return context;
}
// Hook para acessar ações
export function useUserActions() {
const context = useContext(UserActionsContext);
if (!context) {
throw new Error('useUserActions deve estar dentro de UserProvider');
}
return context;
}
// Componente que só lê dados - re-render apenas quando user/loading mudam
function UserCard() {
const { user, loading } = useUserData();
if (loading) return <div>Carregando...</div>;
return <div>{user?.name}</div>;
}
// Componente que precisa apenas das ações
function LoginButton() {
const { login } = useUserActions();
return (
<button onClick={() => login('test@example.com', 'password')}>
Login
</button>
);
}</code></pre>
<p>A beleza deste padrão é que <code>LoginButton</code> nunca sofre re-render quando os dados de usuário mudam, porque consome apenas <code>UserActionsContext</code>, que tem referência estável graças ao <code>useCallback</code>.</p>
<h2>Evitando Re-renders Desnecessários: Técnicas Avançadas</h2>
<h3>Selector Pattern com useContext</h3>
<p>Quando você realmente precisa de um Context grande, use selectors para extrair apenas a parte que precisa:</p>
<pre><code class="language-javascript"></code></pre>
<h3>Atom Pattern com Composição</h3>
<p>Para máxima granularidade, implemente um padrão de átomos similares ao Jotai:</p>
<pre><code class="language-javascript">// Sistema de átomos minimalista
const atomStore = new Map();
function createAtom(key, initialValue) {
const context = createContext(initialValue);
atomStore.set(key, context);
return { context, key };
}
export const themeAtom = createAtom('theme', 'light');
export const userAtom = createAtom('user', null);
function AtomProvider({ atom, initialValue, children }) {
const [value, setValue] = useState(initialValue);
const atomValue = useMemo(
() => ({ value, setValue }),
[value]
);
return (
<atom.context.Provider value={atomValue}>
{children}
</atom.context.Provider>
);
}
// Uso
function App() {
return (
<AtomProvider atom={themeAtom} initialValue="light">
<AtomProvider atom={userAtom} initialValue={null}>
<Header />
<Profile />
</AtomProvider>
</AtomProvider>
);
}
// Hook para consumir átomos
function useAtom(atom) {
const { value, setValue } = useContext(atom.context);
return [value, setValue];
}
function Header() {
const [theme, setTheme] = useAtom(themeAtom);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
{theme}
</button>
);
}</code></pre>
<h2>Conclusão</h2>
<p>Ao trabalhar com <code>useContext</code> em aplicações escaláveis, três princípios transformam seu código de uma bomba de re-renders para uma máquina bem-oleada. Primeiro, <strong>sempre use Context Splitting</strong>: divida grandes Contexts em especializados por domínio. Segundo, <strong>memoize agressivamente</strong> com <code>useMemo</code> e <code>useCallback</code>, garantindo que referências de objetos e funções só mudem quando realmente necessário. Terceiro, <strong>considere separar dados de ações</strong> quando seu estado é volátil, mantendo ações com referência estável enquanto dados variam. Essas três técnicas combinadas eliminam praticamente todos os re-renders desnecessários sem você precisar migrar para Redux ou Zustand.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://react.dev/reference/react/useContext" target="_blank" rel="noopener noreferrer">React Documentation: useContext</a></li>
<li><a href="https://react.dev/reference/react/useMemo" target="_blank" rel="noopener noreferrer">React Documentation: useMemo</a></li>
<li><a href="https://kentcdodds.com/blog/how-to-use-react-context-effectively" target="_blank" rel="noopener noreferrer">Kent C. Dodds - How to use React Context effectively</a></li>
<li><a href="https://web.dev/react/" target="_blank" rel="noopener noreferrer">Web Dev: React Context API Performance</a></li>
<li><a href="https://jotai.org/" target="_blank" rel="noopener noreferrer">Jotai - Primitive and Flexible State Management</a></li>
</ul>
<p><!-- FIM --></p>