Пошук уроків, статей та іншого контенту
Додайте Client Components для стану, обробників подій, ефектів та браузерних API.
У Next.js компоненти за замовчуванням є Server Components. Вони підходять для:
отримання даних на сервері;
формування HTML;
роботи з серверними ресурсами;
зменшення кількості JavaScript у браузері.
Client Components потрібні, коли компонент має працювати в браузері та використовувати:
стан через useState;
обробники подій: onClick, onChange, onSubmit;
ефекти через useEffect;
браузерні API: window, document, localStorage, navigator;
інтерактивні React-компоненти, які залежать від цих можливостей.
Щоб позначити компонент як клієнтський, на початку файлу потрібно додати директиву:
"use client";Вона має бути першим оператором у файлі, до імпортів.
Розглянемо компонент лічильника, який:
зберігає значення у стані;
змінює стан після натискання кнопок;
зберігає значення в localStorage;
змінює заголовок вкладки через document.title;
відстежує статус підключення браузера до мережі.
Створіть файл app/components/InteractiveCounter.tsx:
"use client";
import { useEffect, useState } from "react";
const STORAGE_KEY = "lesson-counter";
export default function InteractiveCounter() {
const [count, setCount] = useState(0);
const [isOnline, setIsOnline] = useState<boolean | null>(null);
useEffect(() => {
const savedCount = window.localStorage.getItem(STORAGE_KEY);
if (savedCount !== null) {
const parsedCount = Number(savedCount);
if (Number.isFinite(parsedCount)) {
setCount(parsedCount);
}
}
}, []);
useEffect(() => {
window.localStorage.setItem(STORAGE_KEY, String(count));
document.title = `Лічильник: ${count}`;
}, [count]);
useEffect(() => {
const updateOnlineStatus = () => {
setIsOnline(window.navigator.onLine);
};
updateOnlineStatus();
window.addEventListener("online", updateOnlineStatus);
window.addEventListener("offline", updateOnlineStatus);
return () => {
window.removeEventListener("online", updateOnlineStatus);
window.removeEventListener("offline", updateOnlineStatus);
};
}, []);
function increase() {
setCount((currentCount) => currentCount + 1);
}
function decrease() {
setCount((currentCount) => currentCount - 1);
}
function reset() {
setCount(0);
}
return (
<section>
<h2>Інтерактивний лічильник</h2>
<p>Поточне значення: {count}</p>
<div>
<button type="button" onClick={decrease}>
−1
</button>
<button type="button" onClick={reset}>
Скинути
</button>
<button type="button" onClick={increase}>
+1
</button>
</div>
<p>
Статус мережі:{" "}
{isOnline === null ? "перевіряємо..." : isOnline ? "онлайн" : "офлайн"}
</p>
</section>
);
}Тепер використайте цей компонент у Server Component, наприклад у app/page.tsx:
import InteractiveCounter from "./components/InteractiveCounter";
export default function HomePage() {
return (
<main>
<h1>Сторінка з клієнтською взаємодією</h1>
<InteractiveCounter />
</main>
);
}page.tsx залишається Server Component. Він може імпортувати й відображати Client Component.
"use client"Директива "use client" визначає межу між серверною та клієнтською частинами дерева компонентів.
Усі компоненти, імпортовані Client Component, також належать до його клієнтської частини, якщо вони не створюють окрему серверну межу.
Тому директиву краще додавати якомога нижче в дереві компонентів — лише до компонента, якому справді потрібна інтерактивність. Це допомагає не надсилати зайвий JavaScript до браузера.
Наприклад, заголовок сторінки не потребує клієнтської логіки:
export default function Page() {
return (
<main>
<h1>Каталог товарів</h1>
<ProductFilter />
</main>
);
}Якщо стан і обробники подій потрібні лише фільтру, "use client" слід додати до файлу ProductFilter, а не до всієї сторінки.
У Client Components можна використовувати React Hooks, зокрема useState.
"use client";
import { useState } from "react";
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button type="button" onClick={() => setLiked((current) => !current)}>
{liked ? "Подобається" : "Вподобати"}
</button>
);
}Функція, передана в onClick, виконується в браузері після події користувача.
Для оновлення стану на основі його попереднього значення використовуйте функціональну форму:
setCount((currentCount) => currentCount + 1);Це надійніше, ніж використовувати значення зі зовнішньої області видимості:
setCount(count + 1);Функціональна форма особливо важлива, коли кілька оновлень стану можуть відбутися поспіль.
useEffectuseEffect використовується для синхронізації компонента із зовнішніми системами:
браузерним API;
підписками на події;
таймерами;
мережевими запитами, якщо вони справді залежать від клієнтської взаємодії;
сторонніми бібліотеками, які працюють лише в браузері.
Ефект із порожнім масивом залежностей виконується після першого монтування компонента:
useEffect(() => {
console.log("Компонент змонтовано");
}, []);Ефект із залежністю count запускається після монтування та після кожної зміни count:
useEffect(() => {
document.title = `Лічильник: ${count}`;
}, [count]);У масиві залежностей потрібно вказувати значення, які використовуються всередині ефекту та можуть змінюватися.
Об’єкти window, document, localStorage і navigator існують у браузері. Їх не можна безпечно використовувати під час виконання компонента на сервері.
Неправильний приклад:
"use client";
const savedTheme = window.localStorage.getItem("theme");Під час початкового формування сторінки Next.js цей код може виконатися в середовищі без window.
Безпечний підхід — звертатися до браузерного API всередині useEffect або обробника події:
useEffect(() => {
const savedTheme = window.localStorage.getItem("theme");
console.log(savedTheme);
}, []);Під час першого відображення компонент використовує початкове значення стану, а після монтування в браузері читає дані з localStorage.
Якщо ефект створює підписку, таймер або інший ресурс, він має повернути функцію очищення.
У прикладі з відстеженням мережі компонент додає обробники:
window.addEventListener("online", updateOnlineStatus);
window.addEventListener("offline", updateOnlineStatus);Після демонтування компонента обробники видаляються:
return () => {
window.removeEventListener("online", updateOnlineStatus);
window.removeEventListener("offline", updateOnlineStatus);
};Очищення запобігає витокам пам’яті та повторній реєстрації обробників, якщо компонент монтується знову.
Server Component може передати Client Component звичайні серіалізовані props:
import ProductCounter from "./ProductCounter";
export default function ProductPage() {
return <ProductCounter productName="Ноутбук" initialCount={1} />;
}Client Component:
"use client";
import { useState } from "react";
type ProductCounterProps = {
productName: string;
initialCount: number;
};
export default function ProductCounter({
productName,
initialCount,
}: ProductCounterProps) {
const [count, setCount] = useState(initialCount);
return (
<div>
<h2>{productName}</h2>
<p>Кількість: {count}</p>
<button type="button" onClick={() => setCount((value) => value + 1)}>
Додати
</button>
</div>
);
}Props для межі між Server і Client Components мають бути серіалізованими. Розробляйте цю межу так, щоб передавати прості значення: рядки, числа, boolean, масиви та об’єкти з такими значеннями.
"use client" розміщено не на початку файлуНеправильно:
import { useState } from "react";
"use client";Правильно:
"use client";
import { useState } from "react";Не звертайтеся до window або localStorage безпосередньо в тілі компонента. Перенесіть такий код у useEffect або обробник події.
Не додавайте "use client" до кореневої сторінки лише через один інтерактивний елемент. Виділіть цей елемент в окремий Client Component.
Якщо компонент додає слухач події або запускає таймер, очищайте його у функції, яку повертає useEffect.
Якщо ефект використовує змінне значення, його зазвичай потрібно додати до масиву залежностей:
useEffect(() => {
document.title = `Лічильник: ${count}`;
}, [count]);Порожній масив залежностей не означає «запускати ефект після кожної зміни». Він означає запуск після монтування.
Client Component позначається директивою "use client" на початку файлу.
Він потрібен для стану, обробників подій, ефектів і браузерних API.
useState зберігає локальний стан компонента.
useEffect синхронізує компонент із зовнішніми системами.
window, document, localStorage та navigator використовуйте в ефектах або обробниках подій.
Підписки та таймери потрібно очищати.
"use client" варто додавати якомога ближче до компонента, якому потрібна інтерактивність.
Server Component може відображати Client Component і передавати йому серіалізовані props.