Загрузка данных в useEffect

Загружаем данные с сервера правильным способом и обходим главную ловушку — бесконечный цикл запросов.

Загрузка данных — это побочный эффект, поэтому её место в useEffect, а результат и статус загрузки хранят в состоянии.

Три состояния загрузки

Любой запрос к серверу проходит фазы: «грузим», «получили данные», «ошибка». Под каждую заводят своё состояние — так UI может показать спиннер, данные или сообщение об ошибке:

function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch("https://api.example.com/users")
      .then((res) => res.json())
      .then((data) => setUsers(data))
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false));
  }, []); // [] — загрузить один раз при появлении

  if (loading) return <p>Загрузка...</p>;
  if (error) return <p>Ошибка: {error}</p>;

  return (
    <ul>
      {users.map((u) => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

Почему пустой массив здесь критичен

Обратите внимание на []. Загрузка должна произойти один раз, при появлении компонента. Без массива зависимостей возникает катастрофа — бесконечный цикл.

Главная ошибка: бесконечный цикл

Разберём, как новичок создаёт бесконечный цикл. Если забыть массив зависимостей, цепочка замкнётся:

// ❌ БЕСКОНЕЧНЫЙ ЦИКЛ
useEffect(() => {
  fetch(url)
    .then((r) => r.json())
    .then((data) => setUsers(data)); // меняет состояние...
}); // ...нет массива → эффект после каждой отрисовки

Логика катастрофы по шагам:

  1. Эффект выполняется → setUsers меняет состояние.
  2. Смена состояния вызывает перерисовку.
  3. Без массива зависимостей эффект запускается после каждой перерисовки → снова fetch → снова setUsers → шаг 2.

Получается лавина запросов. Лечится одной строкой — пустым массивом [], который говорит «загрузи только один раз».

Загрузка при смене параметра

Если данные зависят от параметра (например, userId), положите его в зависимости — тогда новые данные подгрузятся при каждой смене:

useEffect(() => {
  fetch(`/api/users/${userId}`)
    .then((r) => r.json())
    .then(setUser);
}, [userId]); // перезагрузка при смене userId

Async внутри эффекта

Саму функцию-эффект нельзя делать async (она не должна возвращать промис — это место для функции очистки). Обходят это вложенной async-функцией:

useEffect(() => {
  async function load() {
    const res = await fetch(url);
    const data = await res.json();
    setUsers(data);
  }
  load();
}, []);

Итог

  • Загрузка данных — это эффект; держите три состояния: data, loading, error.
  • Пустой массив [] загружает один раз; зависимость [userId] — перезагружает при смене параметра.
  • Без массива зависимостей setState внутри эффекта создаёт бесконечный цикл запросов.
Проверьте себя
1. Где правильно выполнять загрузку данных с сервера?
AПрямо в теле компонента
BВ useEffect, сохраняя результат в состояние
CВ функции рендера JSX
DВ массиве зависимостей
2. Из-за чего возникает бесконечный цикл запросов в useEffect?
AИз-за использования fetch
BЭффект без массива зависимостей вызывает setState, что перерисовывает компонент и снова запускает эффект
CИз-за пустого массива []
DИз-за catch
3. Что нужно передать в массив зависимостей, чтобы данные перезагружались при смене userId?
A[]
B[userId]
Cничего не указывать
D[users]
Поддержать проект