Client Components
Adicione interatividade com "use client" -- hooks, manipuladores de eventos e APIs do navegador.
Busque em todas as páginas da documentação
Adicione interatividade com "use client" -- hooks, manipuladores de eventos e APIs do navegador.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida -- pronto para copiar e colar.
// app/components/counter.tsx
"use client";
import { useState } from "react";
export function Counter({ initialCount = 0 }: { initialCount?: number }) {
const [count, setCount] = useState(initialCount);
return (
<button onClick={() => setCount((c) => c + 1)}>
Count: {count}
</button>
);
}// app/page.tsx (Server Component importa o Client Component)
import { Counter } from "./components/counter";
export default function Page() {
return (
<main>
<h1>Welcome</h1>
<Counter initialCount={5} />
</main>
);
}Quando usar isso: Você precisa de useState, useEffect, useRef, manipuladores de eventos (onClick, onChange) ou APIs do navegador (window, localStorage, IntersectionObserver).
// app/components/search-autocomplete.tsx
"use client";
import { useState, useEffect, useRef, useTransition } from "react";
import { useRouter } from "next/navigation";
type Suggestion = { id: string; label: string };
export function SearchAutocomplete() {
const [query, setQuery] = useState("");
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
const [isOpen, setIsOpen] = useState(false);
const [isPending, startTransition] = useTransition();
const inputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
useEffect(() => {
if (query.length < 2) {
setSuggestions([]);
return;
}
const controller = new AbortController();
fetch(`/api/suggestions?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
})
.then((res) => res.json())
.then((data) => setSuggestions(data))
.catch(() => {}); // ignora erros de abort
return () => controller.abort();
}, [query]);
function handleSelect(suggestion: Suggestion) {
setQuery(suggestion.label);
setIsOpen(false);
startTransition(() => {
router.push(`/search?q=${encodeURIComponent(suggestion.label)}`);
});
}
return (
<div className="relative w-full max-w-md">
<input
ref={inputRef}
type="search"
value={query}
onChange={(e) => {
setQuery(e.target.value);
setIsOpen(true);
}}
onFocus={() => setIsOpen(true)}
placeholder="Search..."
className="w-full border rounded px-4 py-2"
/>
{isOpen && suggestions.length > 0 && (
<ul className="absolute top-full left-0 right-0 bg-white border rounded-b shadow-lg z-10">
{suggestions.map((s) => (
<li key={s.id}>
<button
onClick={() => handleSelect(s)}
className="w-full text-left px-4 py-2 hover:bg-gray-100"
>
{s.label}
</button>
</li>
))}
</ul>
)}
{isPending && (
<span className="absolute right-3 top-2.5 text-sm text-gray-400">
Loading...
</span>
)}
</div>
);
}// app/search/page.tsx (Server Component)
import { SearchAutocomplete } from "@/app/components/search-autocomplete";
export default function SearchPage() {
return (
<main className="p-6">
<h1 className="text-2xl font-bold mb-4">Search</h1>
<SearchAutocomplete />
</main>
);
}O que isso demonstra:
"use client" no topo do arquivouseState, useEffect, useRef, useTransitiononChange, onFocus, onClickAbortControlleruseRouter para navegação programática"use client" no topo de um arquivo o marca como um limite do cliente. O componente e todas as suas importações são incluídos no bundle JavaScript do cliente."use client" se aplica ao arquivo, não a um único componente. Todas as exportações desse arquivo se tornam Client Components."use client" no filho). Eles não podem importar Server Components diretamente.Entrada de formulário controlada:
"use client";
import { useState } from "react";
export function EmailForm({ onSubmitAction }: { onSubmitAction: (email: string) => Promise<void> }) {
const [email, setEmail] = useState("");
return (
<form action={async () => { await onSubmitAction(email); }}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Subscribe</button>
</form>
);
}Usando APIs do navegador com segurança:
"use client";
import { useEffect, useState } from "react";
export function WindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
function handleResize() {
setSize({ width: window.innerWidth, height: window.innerHeight });
}
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <p>Window: {size.width} x {size.height}</p>;
}Envolvendo uma biblioteca de cliente de terceiros:
// app/components/map.tsx
"use client";
import { MapContainer, TileLayer, Marker } from "react-leaflet";
export function Map({ lat, lng }: { lat: number; lng: number }) {
return (
<MapContainer center={[lat, lng]} zoom={13}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<Marker position={[lat, lng]} />
</MapContainer>
);
}// Props do servidor devem ser serializáveis
type ClientProps = {
initialData: string[]; // OK
count: number; // OK
serverAction: (id: string) => Promise<void>; // OK (Server Action)
// onClick: () => void; // NÃO OK (função regular)
// ref: React.Ref<T>; // NÃO OK (não serializável)
};
// Tipos de manipulador de eventos
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { ... }
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { ... }
function handleSubmit(e: React.FormEvent<HTMLFormElement>) { ... }"use client" não significa "somente cliente" -- Client Components ainda são renderizados no servidor (SSR) na solicitação inicial. Eles rodam tanto no servidor quanto no cliente. Correção: Se você precisa de renderização exclusivamente para o cliente, use dynamic(() => import("..."), { ssr: false }).
Dessincronização de hidratação -- Se o HTML renderizado no servidor diferir do render do cliente (por exemplo, usando Date.now(), Math.random() ou verificações de window), o React registrará um erro de hidratação. Correção: Use useEffect para valores que diferem entre servidor e cliente, ou suppressHydrationWarning para dessincronizações intencionais.
Importar um Server Component em um Client Component -- Isso não é permitido. A importação será tratada como um Client Component. Correção: Passe o Server Component como children ou outra prop JSX em vez disso.
Aumento do tamanho do bundle -- Tudo importado em um arquivo "use client" acaba no bundle do cliente, incluindo bibliotecas utilitárias. Correção: Mantenha os arquivos "use client" pequenos e focados. Importe bibliotecas pesadas apenas onde necessário.
Todas as exportações se tornam componentes do cliente -- Se você exportar tanto uma função utilitária quanto um componente de um arquivo "use client", a utilidade também se tornará exclusiva para o cliente. Correção: Mantenha utilitários em arquivos separados, não do cliente.
Hooks não podem ser condicionais -- Hooks do React devem ser chamados na mesma ordem a cada renderização. Correção: Nunca coloque hooks dentro de blocos if, loops ou retornos antecipados.
| Abordagem | Use Quando | Não Use Quando |
|---|---|---|
| Client Components | UI interativa com estado, efeitos ou APIs do navegador | Exibição pura de dados sem interatividade |
| Server Components | Renderização somente leitura, busca de dados, sem JS enviado | Você precisa de hooks ou manipuladores de eventos |
dynamic(import, { ssr: false }) | O componente nunca deve renderizar no servidor (por exemplo, libs de canvas) | SSR está bom e você só precisa de hidratação |
| Web Components | Você precisa de elementos interativos agnósticos de framework | Componentes React funcionam para seu caso de uso |
Server Actions (form action) | Você pode lidar com a interação com um envio de formulário | Você precisa de feedback em tempo real do lado do cliente |
Não. Client Components são renderizados no servidor na carga inicial da página (SSR). O servidor envia HTML, então o React o hidrata no cliente para anexar manipuladores de eventos e torná-lo interativo.
Use um Client Component quando precisar de:
useState, useEffect, useRef, useTransition)onClick, onChange, onFocus)window, localStorage, IntersectionObserver)Envolva o uso da API do navegador em useEffect para que ele só rode no cliente após a hidratação:
"use client";
import { useEffect, useState } from "react";
export function WindowSize() {
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <p>Width: {width}</p>;
}Não. Importar um Server Component dentro de um arquivo "use client" o converte silenciosamente em um Client Component. Em vez disso, passe o Server Component como children ou outra prop JSX de um componente pai Server Component.
Porque "use client" puxa todas as importações naquele arquivo para o bundle do cliente, incluindo bibliotecas utilitárias. Corrija isso mantendo os arquivos "use client" pequenos e focados -- extraia lógica pesada para arquivos separados que não sejam do cliente.
Erros de hidratação ocorrem quando o HTML renderizado no servidor difere do render do cliente. Causas comuns:
Date.now() ou Math.random() durante a renderizaçãowindow durante a renderização em vez de em useEffectuseEffect, ou use suppressHydrationWarning para dessincronizações intencionais.Use next/dynamic com ssr: false:
import dynamic from "next/dynamic";
const ClientOnlyMap = dynamic(
() => import("./map"),
{ ssr: false }
);function handleClick(e: React.MouseEvent<HTMLButtonElement>) { }
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { }
function handleSubmit(e: React.FormEvent<HTMLFormElement>) { }Server Actions são tipadas como funções assíncronas que retornam uma Promise:
type FormProps = {
onSubmitAction: (email: string) => Promise<void>;
};Funções regulares (não Server Actions) não são válidas como props serializáveis.
AbortController cancela requisições fetch em andamento quando a limpeza do useEffect é executada (por exemplo, quando query muda)..catch(() => {}) ignora o erro de abort.useTransition permite marcar uma atualização de estado como não urgente para que ela não bloqueie a UI.isPending é true enquanto a transição está em execução, o que é útil para mostrar indicadores de carregamento.router.push() para evitar o bloqueio da entrada enquanto navega.useSearchParams em Client ComponentsRevisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥