Пошук уроків, статей та іншого контенту
Навчитеся безпечно отримувати дані під час монтування компонента за допомогою useEffect.
useEffectКомпонент часто має отримати дані після того, як він з’явився на сторінці:
список товарів;
профіль користувача;
коментарі до статті;
дані для інформаційної панелі.
Для таких побічних дій у React використовують useEffect. Запит до сервера є побічною дією, тому його не варто виконувати безпосередньо під час рендерингу компонента.
useEffect(() => {
// Тут можна виконати запит до сервера
}, []);Порожній масив залежностей [] означає, що ефект запускається після першого монтування компонента.
Поки запит виконується, користувачу потрібно показати відповідний стан. Зазвичай компонент має три основні стани:
завантаження;
успішно отримані дані;
помилка.
Для цього використовують кілька змінних стану:
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);items зберігає отримані дані;
isLoading показує, чи виконується запит;
error зберігає повідомлення про помилку.
useEffectКолбек, переданий у useEffect, не потрібно оголошувати як async безпосередньо. Замість цього асинхронну функцію можна створити всередині ефекту та викликати її:
useEffect(() => {
async function loadData() {
// Асинхронний код
}
loadData();
}, []);Це важливо, тому що useEffect очікує від колбека або нічого, або функцію очищення. Асинхронна функція завжди повертає Promise, а Promise не є функцією очищення.
import { useEffect, useState } from "react";
export default function PostsList() {
const [posts, setPosts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
let ignoreResult = false;
async function loadPosts() {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts?_limit=5",
{
signal: controller.signal,
}
);
if (!response.ok) {
throw new Error("Не вдалося отримати дописи");
}
const data = await response.json();
// Не оновлюємо стан, якщо компонент уже розмонтовано
if (!ignoreResult) {
setPosts(data);
}
} catch (error) {
// Скасування запиту під час очищення не вважаємо помилкою
if (error.name !== "AbortError" && !ignoreResult) {
setError(error.message);
}
} finally {
if (!ignoreResult) {
setIsLoading(false);
}
}
}
loadPosts();
return () => {
// Скасовуємо запит і забороняємо оновлення стану
ignoreResult = true;
controller.abort();
};
}, []);
if (isLoading) {
return <p>Завантаження дописів...</p>;
}
if (error) {
return <p>Помилка: {error}</p>;
}
return (
<section>
<h1>Дописи</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</section>
);
}Компонент рендериться з початковими значеннями стану.
Після монтування React запускає useEffect.
loadPosts виконує HTTP-запит за допомогою fetch.
Поки запит триває, компонент показує Завантаження дописів....
Після успішної відповіді дані записуються через setPosts.
Якщо запит завершився помилкою, повідомлення записується через setError.
Після отримання даних або помилки isLoading стає false.
fetch не вважає HTTP-помилки, наприклад 404 або 500, винятками автоматично. Тому потрібно перевіряти response.ok:
const response = await fetch("/api/posts");
if (!response.ok) {
throw new Error("Помилка сервера");
}
const data = await response.json();Без цієї перевірки код може спробувати обробити помилкову відповідь як звичайні дані.
useEffect може повернути функцію очищення:
useEffect(() => {
// Запуск ефекту
return () => {
// Очищення ефекту
};
}, []);Функція очищення виконується, коли компонент розмонтовується або перед повторним запуском ефекту.
Під час отримання даних компонент може бути видалений зі сторінки раніше, ніж завершиться запит. AbortController дозволяє скасувати запит:
const controller = new AbortController();
fetch("/api/posts", {
signal: controller.signal,
});
return () => {
controller.abort();
};У прикладі також використано змінну ignoreResult. Вона не дозволяє оновити стан після того, як компонент більше не має відображатися.
Ефект із порожнім масивом залежностей:
useEffect(() => {
// Запит
}, []);запускається після монтування конкретного екземпляра компонента.
Якщо запит залежить від значення, це значення потрібно додати до масиву залежностей:
useEffect(() => {
async function loadUser() {
// Запит для конкретного користувача
}
loadUser();
}, [userId]);У такому випадку ефект запуститься повторно, коли зміниться userId.
function PostsList() {
fetch("/api/posts");
return <div>Список</div>;
}Це неправильно, тому що кожен рендер може створювати новий запит.
Правильно виконувати запит у useEffect:
useEffect(() => {
// Запит після монтування
}, []);asyncНе варто писати так:
useEffect(async () => {
// Запит
}, []);Потрібно створити асинхронну функцію всередині ефекту:
useEffect(() => {
async function loadData() {
// Запит
}
loadData();
}, []);Мережа може бути недоступною, сервер може повернути помилку, а відповідь може мати неочікуваний формат. Використовуйте try...catch і зберігайте помилку в стані.
Якщо не показувати стан завантаження, користувач може побачити порожній список і не зрозуміти, чи дані ще завантажуються, чи їх справді немає.
Якщо компонент може зникнути до завершення запиту, використовуйте AbortController та функцію очищення ефекту.
Запити до сервера є побічними діями, тому їх виконують у useEffect.
Для запиту під час монтування використовують порожній масив залежностей [].
Асинхронну функцію потрібно створювати всередині колбека useEffect.
Варто зберігати окремі стани для даних, завантаження та помилки.
Перевіряйте response.ok перед обробкою відповіді.
Для безпечного завершення роботи використовуйте AbortController і функцію очищення.
Не оновлюйте стан після того, як компонент було розмонтовано.