Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Загрузка данных из API

Большинство приложений получают данные с сервера через API — адрес, который возвращает JSON. В React запрос отправляют в эффекте, а ответ кладут в состояние. Пока ответа нет, показывают «Загрузка…», а если запрос не удался — сообщение об ошибке. С этого урока начинается второй пример курса — небольшой блог.

Новый проект

Создайте проект блога рядом со списком задач:

npm create vite@latest blog -- --template react
cd blog
npm install
npm run dev

Статьи будем брать из JSONPlaceholder — бесплатного учебного API. Адрес https://jsonplaceholder.typicode.com/posts возвращает массив из 100 статей с полями id, title и body.

Простой запрос

Создайте src/components/PostList.jsx:

// src/components/PostList.jsx
import { useEffect, useState } from 'react';
 
export default function PostList() {
  const [posts, setPosts] = useState([]);
 
  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then((response) => response.json())
      .then((data) => setPosts(data));
  }, []);
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Выведите <PostList /> в src/App.jsx. Через мгновение на странице появится список из 100 заголовков. Пустой массив зависимостей означает, что запрос отправится один раз.

Загрузка и ошибка

Перепишем компонент с async/await и тремя состояниями:

// src/components/PostList.jsx
import { useEffect, useState } from 'react';
 
export default function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    async function load() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts');
        if (!response.ok) {
          throw new Error(`Ошибка ${response.status}`);
        }
        setPosts(await response.json());
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
 
    load();
  }, []);
 
  if (loading) return <p>Загрузка…</p>;
  if (error) return <p>Не удалось загрузить статьи: {error}</p>;
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Сначала на странице «Загрузка…», затем список. Измените адрес на несуществующий, например /postsx, — появится «Не удалось загрузить статьи: Ошибка 404». fetch не бросает исключение при кодах 404 и 500, поэтому response.ok проверяют вручную.

Запрос, зависящий от пропса

Если адрес зависит от пропса, его добавляют в зависимости, а в очистке отмечают, что старый ответ больше не нужен:

// src/components/PostComments.jsx
useEffect(() => {
  let ignore = false;
 
  fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`)
    .then((response) => response.json())
    .then((data) => {
      if (!ignore) setComments(data);
    });
 
  return () => {
    ignore = true;
  };
}, [postId]);

Если postId сменится раньше, чем придёт ответ, старый ответ будет проигнорирован и не перезапишет новые комментарии.

Частая ошибка

Функцию эффекта нельзя объявлять async: она должна вернуть функцию очистки или ничего, а async-функция всегда возвращает промис.

// Неправильно:
useEffect(async () => {
  const response = await fetch(url);
}, []);

Объявите async-функцию внутри эффекта и вызовите её, как в примере выше. В реальных проектах загрузку часто доверяют библиотеке TanStack Query — она описана в последней главе.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.