// arquitetura, composição e boas práticas
Patterns arquiteturais de React usados em projetos profissionais: Compound Components, Custom Hooks, Render Props, HOCs e mais — com exemplos práticos.
// patterns para componentes flexíveis
Patterns que tornam seus componentes mais flexíveis, reutilizáveis e fáceis de manter.
Componentes que trabalham juntos compartilhando estado implícito via Context. Permite API declarativa e flexível.
// API declarativa e limpa<Select><Select.Trigger>Escolha uma opção</Select.Trigger><Select.Content><Select.Item value="react">React</Select.Item><Select.Item value="vue">Vue</Select.Item><Select.Item value="angular">Angular</Select.Item></Select.Content></Select>// Implementação com Contextconst SelectContext = createContext<SelectState | null>(null);function Select({ children }: { children: ReactNode }) {const [value, setValue] = useState('');return (<SelectContext.Provider value={{ value, setValue }}>{children}</SelectContext.Provider>);}Select.Trigger = function Trigger({ children }) { /* ... */ };Select.Content = function Content({ children }) { /* ... */ };Select.Item = function Item({ value, children }) { /* ... */ };
Passa uma função como prop (ou children) que recebe dados e retorna JSX. Útil para injetar lógica sem impor UI.
// O componente fornece dados, o consumidor decide a UIfunction MouseTracker({ children }: {children: (pos: { x: number; y: number }) => ReactNode}) {const [pos, setPos] = useState({ x: 0, y: 0 });useEffect(() => {const handler = (e: MouseEvent) =>setPos({ x: e.clientX, y: e.clientY });window.addEventListener('mousemove', handler);return () => window.removeEventListener('mousemove', handler);}, []);return <>{children(pos)}</>;}// Uso<MouseTracker>{({ x, y }) => (<div>Mouse: {x}, {y}</div>)}</MouseTracker>
Separe lógica (Container) de apresentação (Presentational). O Container busca dados e gerencia estado, o Presentational só renderiza.
// Container — lógica e dadosfunction UserListContainer() {const { data, isLoading } = useQuery({queryKey: ['users'],queryFn: fetchUsers});if (isLoading) return <Skeleton />;return <UserList users={data ?? []} />;}// Presentational — só UI, sem lógicafunction UserList({ users }: { users: User[] }) {return (<ul>{users.map(user => (<li key={user.id}>{user.name}</li>))}</ul>);}
// extraia e reutilize lógica
Custom hooks encapsulam lógica reutilizável. São a forma mais idiomática de compartilhar comportamento entre componentes no React.
useLocalStoragePersiste estado no localStorage com sincronização automática.
function useLocalStorage<T>(key: string, initial: T) {const [value, setValue] = useState<T>(() => {if (typeof window === 'undefined') return initial;const stored = localStorage.getItem(key);return stored ? JSON.parse(stored) : initial;});useEffect(() => {localStorage.setItem(key, JSON.stringify(value));}, [key, value]);return [value, setValue] as const;}// Usoconst [theme, setTheme] = useLocalStorage('theme', 'dark');
useDebounceAtrasa a atualização de um valor — ideal para buscas e inputs.
function useDebounce<T>(value: T, delay: number): T {const [debounced, setDebounced] = useState(value);useEffect(() => {const timer = setTimeout(() => setDebounced(value), delay);return () => clearTimeout(timer);}, [value, delay]);return debounced;}// Usoconst [search, setSearch] = useState('');const debouncedSearch = useDebounce(search, 300);useEffect(() => {fetchResults(debouncedSearch);}, [debouncedSearch]);
useMediaQueryReage a mudanças de media query (responsividade) no JavaScript.
function useMediaQuery(query: string): boolean {const [matches, setMatches] = useState(false);useEffect(() => {const media = window.matchMedia(query);setMatches(media.matches);const listener = (e: MediaQueryListEvent) =>setMatches(e.matches);media.addEventListener('change', listener);return () => media.removeEventListener('change', listener);}, [query]);return matches;}// Usoconst isMobile = useMediaQuery('(max-width: 768px)');
useClickOutsideDetecta cliques fora de um elemento — perfeito para modais e dropdowns.
function useClickOutside(ref: RefObject<HTMLElement | null>,handler: () => void) {useEffect(() => {const listener = (e: MouseEvent | TouchEvent) => {if (!ref.current?.contains(e.target as Node)) {handler();}};document.addEventListener('mousedown', listener);document.addEventListener('touchstart', listener);return () => {document.removeEventListener('mousedown', listener);document.removeEventListener('touchstart', listener);};}, [ref, handler]);}// Usoconst ref = useRef<HTMLDivElement>(null);useClickOutside(ref, () => setOpen(false));
// gerencie estado de forma escalável
Patterns para gerenciar estado complexo de forma organizada e previsível.
Use useReducer para estado complexo com múltiplas ações. Mais previsível que múltiplos useState.
// Múltiplos useState — difícil de manterconst [items, setItems] = useState([]);const [loading, setLoading] = useState(false);const [error, setError] = useState(null);const [page, setPage] = useState(1);// Atualização espalhada pelo componentesetLoading(true);setError(null);fetch(url).then(data => { setItems(data); setLoading(false); }).catch(err => { setError(err); setLoading(false); });
Compartilhe estado global com tipo seguro via Context. Evita prop drilling em árvores profundas.
// Prop drilling — 4 níveis de profundidade<App user={user} /><Layout user={user} /><Sidebar user={user} /><UserMenu user={user} /><Avatar name={user.name} />
// otimize renders e carregamento
Técnicas para evitar re-renders desnecessários e otimizar carregamento.
Evite re-renders de componentes filhos quando props não mudam.
// Componente memoizado — só re-renderiza se props mudaremconst ExpensiveList = memo(function ExpensiveList({items,onItemClick,}: {items: Item[];onItemClick: (id: string) => void;}) {return (<ul>{items.map(item => (<li key={item.id} onClick={() => onItemClick(item.id)}>{item.name}</li>))}</ul>);});// Parent estabiliza a referência da funçãofunction Parent() {const [items] = useState(fetchItems());const handleClick = useCallback((id: string) => {console.log('clicked', id);}, []);return <ExpensiveList items={items} onItemClick={handleClick} />;}
Memorize resultados de cálculos caros para evitar recomputação a cada render.
function Dashboard({ transactions }: { transactions: Transaction[] }) {// Só recalcula quando transactions mudaconst stats = useMemo(() => ({total: transactions.reduce((sum, t) => sum + t.amount, 0),average: transactions.reduce((sum, t) => sum + t.amount, 0)/ transactions.length,max: Math.max(...transactions.map(t => t.amount)),byCategory: groupBy(transactions, 'category'),}), [transactions]);return (<div><StatCard label="Total" value={stats.total} /><StatCard label="Média" value={stats.average} /></div>);}
Carregue componentes pesados sob demanda para reduzir o bundle inicial.
import { lazy, Suspense } from 'react';// Carregado apenas quando necessárioconst HeavyChart = lazy(() => import('./HeavyChart'));const MarkdownEditor = lazy(() => import('./MarkdownEditor'));function App() {const [showChart, setShowChart] = useState(false);return (<div><button onClick={() => setShowChart(true)}>Mostrar Gráfico</button>{showChart && (<Suspense fallback={<Skeleton className="h-64" />}><HeavyChart data={data} /></Suspense>)}</div>);}