JavaRush /Курсы /Модуль 3: React /Обработка состояния загрузки и отображение индикаторов

Обработка состояния загрузки и отображение индикаторов

Модуль 3: React
11 уровень , 9 лекция
Открыта

Введение

Представьте, что вы нажали кнопку для получения данных, и ничего не произошло. Экран замер, и вы начнете паниковать: сломался интернет? или сервер упал? или приложение вообще не работает? Теперь представьте вместо этого кружок «Загрузка…». У вас сразу появится уверенность, что процесс идет, и вы просто ждете.

Итак, управление состоянием загрузки:

  • Создает предсказуемость интерфейса.
  • Улучшает пользовательский опыт (UX).
  • Дает обратную связь о состоянии приложения.
  • Помогает отлавливать ошибки при долгих задержках.

Создаем индикатор загрузки шаг за шагом

1. Управление состоянием с useState

Для отображения индикатора загрузки нам нужно знать, происходит ли запрос в данный момент. Для этого мы используем штатный React-хук useState.

Вот базовый пример обработки состояния загрузки с fetch:

import React, { useState } from 'react';

const FetchWithLoading: React.FC = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState<null | string>(null);

  const fetchData = async () => {
    setIsLoading(true); // Включаем индикатор загрузки
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
      const result = await response.json();
      setData(result.title); // Сохраняем данные
    } catch (error) {
      console.error('Ошибка загрузки:', error);
    } finally {
      setIsLoading(false); // Выключаем индикатор загрузки
    }
  };

  return (
    <div>
      <button onClick={fetchData}>Загрузить данные</button>
      {isLoading && <p>Загрузка...</p>}
      {data && <p>Результат: {data}</p>}
    </div>
  );
};

export default FetchWithLoading;

Разберем код:

  1. Состояние isLoading:
    • Управляет отображением индикатора загрузки: true, пока идёт запрос.
    • Устанавливаем true перед началом запроса и сбрасываем в false после завершения (в finally).
  2. Индикатор Загрузка...:
    • Условный рендеринг: отображаем текст, если isLoading === true.

2. Разделяем логику и UI через кастомные хуки

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

import { useState } from 'react';

export const useFetchWithLoading = (url: string) => {
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState<null | any>(null);
  const [error, setError] = useState<string | null>(null);

  const fetchData = async () => {
    setIsLoading(true);
    setError(null); // Сбрасываем предыдущую ошибку
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`Ошибка HTTP: ${response.status}`);
      }
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setIsLoading(false);
    }
  };

  return { isLoading, data, error, fetchData };
};

Теперь используем наш хук в компоненте:

import React from 'react';
import { useFetchWithLoading } from './useFetchWithLoading';

const DataWithCustomHook: React.FC = () => {
  const { isLoading, data, error, fetchData } = useFetchWithLoading(
    'https://jsonplaceholder.typicode.com/posts/1'
  );

  return (
    <div>
      <button onClick={fetchData}>Загрузить данные</button>
      {isLoading && <p>Загрузка...</p>}
      {error && <p style={{ color: 'red' }}>Ошибка: {error}</p>}
      {data && <p>Результат: {data.title}</p>}
    </div>
  );
};

export default DataWithCustomHook;

Теперь, если мы захотим подключить другую API, нам достаточно передать новый url в хук.

Добавляем "визуальную магию"

Серое "Загрузка..." — это скучно. Давайте сделаем что-то более визуально привлекательное. Например, используем спиннер.

Используем CSS-анимацию

/* spinner.css */
.spinner {
  border: 4px solid rgba(0, 0, 0, 0.1);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border-left-color: #09f;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

Теперь добавим этот спиннер в наш компонент:

import React from 'react';
import './spinner.css';
import { useFetchWithLoading } from './useFetchWithLoading';

const FancyLoader: React.FC = () => {
  const { isLoading, data, error, fetchData } = useFetchWithLoading(
    'https://jsonplaceholder.typicode.com/posts/1'
  );

  return (
    <div>
      <button onClick={fetchData}>Загрузить данные</button>
      {isLoading && <div className="spinner"></div>}
      {error && <p style={{ color: 'red' }}>Ошибка: {error}</p>}
      {data && <p>Результат: {data.title}</p>}
    </div>
  );
};

export default FancyLoader;

Теперь вместо текста "Загрузка..." у нас будет стильный вращающийся спиннер.

Отображение прогресса загрузки (если доступно)

Для загрузки больших файлов можно показывать прогресс. Давайте попробуем это реализовать:

const fetchWithProgress = async (url: string, onProgress: (progress: number) => void) => {
  const response = await fetch(url);
  const reader = response.body?.getReader();
  const contentLength = Number(response.headers.get('Content-Length'));

  let receivedLength = 0;
  const chunks: Uint8Array[] = [];

  while (true) {
    const { done, value } = await reader!.read();
    if (done) break;
    chunks.push(value!);
    receivedLength += value!.length;
    onProgress((receivedLength / contentLength) * 100);
  }

  const blob = new Blob(chunks);
  return blob.text();
};

Используем функцию в компоненте:

import React, { useState } from 'react';

const ProgressLoader: React.FC = () => {
  const [progress, setProgress] = useState(0);

  const loadData = async () => {
    const data = await fetchWithProgress('https://example.com/large-file', setProgress);
    console.log(data);
  };

  return (
    <div>
      <button onClick={loadData}>Загрузить данные</button>
      <p>Прогресс загрузки: {progress.toFixed(2)}%</p>
    </div>
  );
};

export default ProgressLoader;

Подводные камни и типичные ошибки

  • Индикатор исчезает слишком рано или слишком поздно? Убедитесь, что состояние isLoading обновляется в правильный момент. Не забывайте использовать finally для сброса состояния в любом исходе.
  • Визуальная перегруженность: не показывайте одновременно индикатор загрузки и результат. Всегда уделяйте внимание UX.
  • Огромное количество запросов: если вы используете индикатор в списках (например, при инфинит-скролле), объедините их состояние в массивы, чтобы избежать хаоса.

Теперь ваш пользовательский интерфейс будет не только функциональным, но и отзывчивым!

1
Задача
Модуль 3: React, 11 уровень, 9 лекция
Недоступна
Создание базового индикатора загрузки
Создание базового индикатора загрузки
1
Задача
Модуль 3: React, 11 уровень, 9 лекция
Недоступна
Использование кастомного хука для загрузки данных
Использование кастомного хука для загрузки данных
3
Опрос
Настройка Axios, 11 уровень, 9 лекция
Недоступен
Настройка Axios
Настройка Axios
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ