// useState, useCallback, useMemo, useRef, Context, Zustand
Guia completo: estado local, hooks de performance (useCallback, useMemo, useRef), useReducer, Context e store (Zustand). Exemplos reais copiáveis e quando usar cada um.
// o ponto de partida
useState é o primeiro passo: o estado vive no componente e desce para os filhos via props. Use para formulários, toggles, UI que não precisa ser acessada em vários níveis da árvore.
Subir estado (lift state up) só quando dois irmãos precisarem do mesmo dado. Se você começar a passar props por muitos níveis (prop drilling), é hora de considerar Context ou uma store.
import { useState } from "react";function Counter() {const [count, setCount] = useState(0);return (<div><span>{count}</span><button onClick={() => setCount((c) => c + 1)}>+1</button></div>);}// Estado vive no componente; os filhos recebem via props se precisar.
function SearchField() {const [query, setQuery] = useState("");return (<inputvalue={query}onChange={(e) => setQuery(e.target.value)}placeholder="Search..."/>);}// Fonte única de verdade; fácil validar ou enviar.
// useCallback, useMemo, useRef, useReducer
useCallback e useMemo evitam recriação de funções e valores entre renders; useRef guarda referência mutável (DOM, timer, valor anterior) sem causar re-render. useReducer centraliza lógica de estado complexa.
useCallback: quando passar callback para filho memoizado ou como dep de useEffect. useMemo: listas filtradas/ordenadas ou cálculos pesados. useRef: focus, intervalId, previous value. useReducer: estado com várias ações ou próximo de uma state machine.
Retorna a mesma função entre renders enquanto as deps não mudarem. Essencial para React.memo + callback e para dependências estáveis em useEffect/useMemo.
import { useCallback, useState } from "react";function Parent() {const [count, setCount] = useState(0);const handleClick = useCallback(() => {setCount((c) => c + 1);}, []); // referência estável: filho memoizado não re-renderizareturn <MemoizedChild onIncrement={handleClick} count={count} />;}
// Deps: incluir todo valor do closure usado dentroconst fetchUser = useCallback(async (id: string) => {const res = await fetch(`/api/users/${id}`, { headers: { Authorization: token } });return res.json();}, [token]); // token de props/state deve estar nas deps
Memoiza o resultado de um cálculo; só recalcula quando as dependências mudam. Use para derivar dados (filter, sort) ou computação custosa.
import { useMemo, useState } from "react";function ProductList({ products, filter }: { products: Product[]; filter: string }) {const filtered = useMemo(() => products.filter((p) => p.name.toLowerCase().includes(filter.toLowerCase())),[products, filter]);return <ul>{filtered.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;}
// Cálculo pesado: só recalcula quando as entradas mudamconst sortedItems = useMemo(() => [...items].sort((a, b) => a.score - b.score),[items]);const stats = useMemo(() => computeHeavyStats(data), [data]);
Referência mutável que persiste entre renders e não dispara re-render. Uso: ref ao DOM, guardar intervalId/timeoutId, padrão "previous value".
import { useRef, useEffect } from "react";function SearchInput() {const inputRef = useRef<HTMLInputElement>(null);useEffect(() => {inputRef.current?.focus();}, []);return <input ref={inputRef} type="search" />;}
// Padrão: guardar valor anterior (ex.: para diff)function usePrevious<T>(value: T): T | undefined {const ref = useRef<T>();useEffect(() => {ref.current = value;}, [value]);return ref.current;}
// Guardar valor mutável que não dispara re-render (ex.: ID do setInterval)const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);useEffect(() => {intervalRef.current = setInterval(tick, 1000);return () => {if (intervalRef.current) clearInterval(intervalRef.current);};}, []);
Estado com atualizações via actions; ideal quando a lógica de transição é complexa ou quando você quer previsibilidade (padrão reducer).
import { useReducer } from "react";type State = { count: number };type Action = { type: "increment" } | { type: "decrement"; by: number };function reducer(state: State, action: Action): State {switch (action.type) {case "increment": return { count: state.count + 1 };case "decrement": return { count: state.count - action.by };default: return state;}}function Counter() {const [state, dispatch] = useReducer(reducer, { count: 0 });return (<><span>{state.count}</span><button onClick={() => dispatch({ type: "increment" })}>+1</button><button onClick={() => dispatch({ type: "decrement", by: 1 })}>-1</button></>);}
// compartilhar sem prop drilling
Context expõe um valor para toda uma subárvore sem passar por cada nível. Ideal para tema, idioma (i18n), usuário logado e dados que muitas telas só leem.
Context não é cache: qualquer mudança no value re-renderiza todos os consumidores. Use para dados que mudam pouco; para estado que atualiza com frequência (ex.: carrinho), prefira Zustand ou similar para evitar re-renders em cascata.
import { createContext, useState } from "react";const ThemeContext = createContext<"light" | "dark">("light");export function ThemeProvider({ children }: { children: React.ReactNode }) {const [theme, setTheme] = useState<"light" | "dark">("light");return (<ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>);}
import { useContext } from "react";function ThemedButton() {const theme = useContext(ThemeContext);return <button className={theme}>Submit</button>;}// Não precisa passar props por cada nível (evita prop drilling).
// store leve fora da árvore
Zustand mantém estado fora da árvore de componentes. Quem assina o store re-renderiza só quando a fatia que usa mudar. API mínima, sem Provider obrigatório, TypeScript-friendly.
Adote quando Context gerar muitos re-renders ou quando o estado for global e atualizado com frequência. Jotai e Redux são alternativas; escolha pelo tamanho do time e da aplicação.
import { create } from "zustand";interface CounterStore {count: number;increment: () => void;}export const useCounterStore = create<CounterStore>((set) => ({count: 0,increment: () => set((s) => ({ count: s.count + 1 })),}));
// Em qualquer componente (sem Provider necessário)function Counter() {const { count, increment } = useCounterStore();return <button onClick={increment}>{count}</button>;}// Só re-renderiza quando a fatia que você usa mudar.
// resumo prático
Nem todo app precisa de Redux. Ordem sugerida: useState → useCallback/useMemo/useRef quando precisar (performance) → Context (compartilhar em vários níveis) → Zustand ou outra store (quando Context não der conta).
Priorize simplicidade: estado local > hooks de performance quando medir > Context > store. Só adicione camadas quando a dor for real (performance, manutenção, testes).
Exemplos por cenário:
// useState: estado só neste componente (ou 1–2 níveis via props)function LoginForm() {const [email, setEmail] = useState("");const [password, setPassword] = useState("");const [loading, setLoading] = useState(false);// Não precisa compartilhar com outras telas → estado local bastareturn (<form onSubmit={handleSubmit}><input value={email} onChange={(e) => setEmail(e.target.value)} /><input type="password" value={password} onChange={...} /><button disabled={loading}>Submit</button></form>);}
// Context: mesmo valor lido em muitos níveis (evita prop drilling)// ex.: tema, idioma, usuário logado — mudam poucofunction App() {return (<ThemeProvider> {/* valor disponível para toda a árvore */}<Layout><Sidebar /> {/* Sidebar e Page consomem tema sem props */}<Page /></Layout></ThemeProvider>);}function Sidebar() {const theme = useContext(ThemeContext); // sem prop drillingreturn <aside className={theme}>...</aside>;}
// Zustand (ou store): estado global que atualiza com frequência// ex.: carrinho, filtros — muitos componentes leem e escrevem// Cada componente re-renderiza só quando a fatia que usa mudarconst useCartStore = create((set) => ({items: [],addItem: (item) => set((s) => ({ items: [...s.items, item] })),}));function Header() {const count = useCartStore((s) => s.items.length); // re-render só se o length mudarreturn <span>{count} items</span>;}function ProductCard({ id }) {const addItem = useCartStore((s) => s.addItem); // referência estávelreturn <button onClick={() => addItem({ id })}>Add</button>;}
Next.js App Router, React Patterns, TypeScript e Git: mais guias práticos na seção Dicas.