Hooks, utilitários e patterns que você vai usar de verdade.
Snippets copy-paste prontos para produção e refatorações reais de código. Sem tutorial básico — apenas código prático que resolve problemas do dia a dia.
Escolha para personalizar o conteúdo abaixo
Selecione seu nível para ver conteúdo personalizado
Dicas práticas e diretas que economizam horas de debug e code review
Nunca use index como key em listas que mudam
Use um ID único e estável (como id do banco). Index causa bugs visuais silenciosos quando itens são reordenados ou removidos — o React reutiliza o DOM errado.
Use optional chaining (?.) em vez de && encadeado
user?.address?.city é mais limpo e seguro que user && user.address && user.address.city. Funciona com chamadas de função também: obj.method?.()
Use <></> em vez de <div> quando só precisa agrupar JSX
Fragments não criam nó extra no DOM. Divs desnecessárias quebram layouts CSS (flex, grid) e poluem a árvore DOM sem razão.
Prefira const sempre — só use let quando realmente precisa reatribuir
const comunica intenção: esse valor não muda. Facilita code review e evita reatribuições acidentais. Em ~95% dos casos, const é suficiente.
Use updater function no setState quando depende do valor anterior
setCount(count + 1) pode usar valor stale em batched updates. setCount(prev => prev + 1) sempre usa o valor mais recente. Essencial em event handlers assíncronos.
Sempre use AbortController em fetches dentro de useEffect
Sem abort, trocar de página rapidamente causa race conditions — a resposta do fetch anterior sobrescreve a nova. AbortController cancela requests pendentes no cleanup.
Use 'as const' para inferir tipos literais em vez de string genérica
const roles = ['admin', 'user'] as const infere tipo readonly ['admin', 'user'] em vez de string[]. Permite extrair union types: type Role = typeof roles[number]
Sempre envolva rotas e seções independentes com Error Boundaries
Um erro em um componente filho derruba toda a árvore React. Error Boundaries isolam crashes — a sidebar pode quebrar sem derrubar a página inteira.
Evite barrel files (index.ts) em projetos grandes — eles matam tree-shaking
Importar de um index.ts força o bundler a avaliar todos os re-exports. Em monorepos, isso pode adicionar centenas de KB ao bundle. Importe direto do arquivo fonte.
Componentes que só mostram dados devem ser Server Components — não adicione 'use client' sem necessidade
Server Components rodam no servidor = zero JavaScript no bundle do client. Só adicione 'use client' quando precisa de hooks, event handlers ou browser APIs.
Prefira composição sobre configuração em componentes públicos
Em vez de <Card showHeader showFooter headerTitle='...' footerAction='...'>, use <Card><CardHeader>...</CardHeader><CardFooter>...</CardFooter></Card>. Mais flexível, tipado e extensível.
Use useSyncExternalStore para integrar stores externos com React 18+
É o hook oficial para sincronizar React com qualquer fonte de dados externa (localStorage, WebSocket, Redux-like stores). Garante consistência com concurrent rendering.
Hooks e utilitários prontos para produção. Copie, cole e adapte aos seus projetos
9 snippets encontrados
Atrasa a atualização de um valor até o usuário parar de digitar
import { useEffect, useState } from 'react'
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(handler)
}, [value, delay])
return debouncedValue
}Formata datas sem dependências usando a Intl API nativa
export function formatDate(
date: Date | string,
locale = 'pt-BR'
): string {
const d = typeof date === 'string' ? new Date(date) : date
return new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}).format(d)
}
export function formatRelative(date: Date | string): string {
const d = typeof date === 'string' ? new Date(date) : date
const now = new Date()
const diff = now.getTime() - d.getTime()
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(diff / 3600000)
const days = Math.floor(diff / 86400000)
if (minutes < 1) return 'now'
if (minutes < 60) return `${minutes}min ago`
if (hours < 24) return `${hours}h ago`
if (days < 7) return `${days}d ago`
return formatDate(d)
}Hook para booleanos com actions nomeadas — mais legível que useState(false)
import { useCallback, useState } from 'react'
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue)
const toggle = useCallback(() => setValue(v => !v), [])
const setTrue = useCallback(() => setValue(true), [])
const setFalse = useCallback(() => setValue(false), [])
return { value, toggle, setTrue, setFalse } as const
}Cria context + hook type-safe sem precisar checar undefined
import {
createContext,
useContext,
type ReactNode
} from 'react'
export function createSafeContext<T>(displayName: string) {
const Context = createContext<T | null>(null)
Context.displayName = displayName
function useContextValue(): T {
const value = useContext(Context)
if (value === null) {
throw new Error(
`use${displayName} must be used within ${displayName}Provider`
)
}
return value
}
function Provider({
value,
children,
}: {
value: T
children: ReactNode
}) {
return (
<Context.Provider value={value}>
{children}
</Context.Provider>
)
}
return [Provider, useContextValue] as const
}Hook responsivo que funciona com SSR/Next.js sem hydration mismatch
import { useEffect, useState } from 'react'
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false)
useEffect(() => {
const media = window.matchMedia(query)
setMatches(media.matches)
function handleChange(e: MediaQueryListEvent) {
setMatches(e.matches)
}
media.addEventListener('change', handleChange)
return () => media.removeEventListener('change', handleChange)
}, [query])
return matches
}
export const breakpoints = {
sm: '(min-width: 640px)',
md: '(min-width: 768px)',
lg: '(min-width: 1024px)',
xl: '(min-width: 1280px)',
dark: '(prefers-color-scheme: dark)',
reducedMotion: '(prefers-reduced-motion: reduce)',
} as constReferência estável de função sem stale closures — resolve o bug mais comum de hooks
import { useCallback, useRef } from 'react'
export function useEventCallback<
Args extends unknown[],
Return
>(fn: (...args: Args) => Return): (...args: Args) => Return {
const ref = useRef(fn)
ref.current = fn
return useCallback(
(...args: Args) => ref.current(...args),
[]
)
}Fetch type-safe com validação runtime — single source of truth
import { z, type ZodType } from 'zod'
type FetchOptions = RequestInit & {
params?: Record<string, string>
}
export async function typedFetch<T>(
url: string,
schema: ZodType<T>,
options: FetchOptions = {}
): Promise<T> {
const { params, ...fetchOptions } = options
const queryString = params
? '?' + new URLSearchParams(params).toString()
: ''
const response = await fetch(`${url}${queryString}`, {
headers: {
'Content-Type': 'application/json',
...fetchOptions.headers,
},
...fetchOptions,
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const data = await response.json()
const parsed = schema.safeParse(data)
if (!parsed.success) {
console.error('API validation failed:', parsed.error.issues)
throw new Error(
`Invalid API response: ${parsed.error.issues.map(i => i.message).join(', ')}`
)
}
return parsed.data
}Pattern TypeScript que torna props impossíveis de usar errado
type ButtonProps = {
children: React.ReactNode
className?: string
} & (
| {
variant: 'solid' | 'outline' | 'ghost'
onClick: () => void
loading?: boolean
disabled?: boolean
href?: never
}
| {
variant: 'link'
href: string
onClick?: never
loading?: never
disabled?: never
}
)
export function Button(props: ButtonProps) {
if (props.variant === 'link') {
return (
<a href={props.href} className={props.className}>
{props.children}
</a>
)
}
return (
<button
onClick={props.onClick}
disabled={props.disabled || props.loading}
className={props.className}
>
{props.loading ? <Spinner /> : props.children}
</button>
)
}Pattern que garante em compile time que todos os cases de uma union foram tratados
function assertNever(value: never): never {
throw new Error(`Unhandled value: ${value}`)
}
type PaymentStatus =
| 'pending'
| 'processing'
| 'completed'
| 'failed'
| 'refunded'
function getStatusConfig(status: PaymentStatus) {
switch (status) {
case 'pending':
return { label: 'Pending', color: 'yellow', icon: Clock }
case 'processing':
return { label: 'Processing', color: 'blue', icon: Loader }
case 'completed':
return { label: 'Completed', color: 'green', icon: Check }
case 'failed':
return { label: 'Failed', color: 'red', icon: X }
case 'refunded':
return { label: 'Refunded', color: 'gray', icon: Undo }
default:
return assertNever(status)
}
}Escolha o problema e descubra o pattern recomendado com código pronto
Patterns reais de refatoração — o tipo de melhoria que faz diferença em code review
Usar index como key em listas causa bugs visuais silenciosos quando itens são reordenados, removidos ou inseridos
function TodoList({ todos, onRemove }) {
return (
<ul>
{todos.map((todo, index) => (
// ❌ index as key — React loses track of items
<li key={index}>
<input
type="checkbox"
defaultChecked={todo.done}
/>
<span>{todo.text}</span>
<button onClick={() => onRemove(index)}>
Remove
</button>
</li>
))}
</ul>
)
}function TodoList({ todos, onRemove }) {
return (
<ul>
{todos.map((todo) => (
// ✅ Unique stable ID — React tracks correctly
<li key={todo.id}>
<input
type="checkbox"
defaultChecked={todo.done}
/>
<span>{todo.text}</span>
<button onClick={() => onRemove(todo.id)}>
Remove
</button>
</li>
))}
</ul>
)
}Confira meus projetos e veja esses patterns aplicados em código real.