Перейти к основному содержимому

Кнопка с загрузкой — React, Promise и поток обновлений

Разработчику

См. также: Первая программа на React · Асинхронность · Генераторы · Утилита wait · Кнопка "Поделиться" (vanilla) · карта React · Web API на практике - примеры кода


Задача

Пользователь нажимает кнопку "Создать заметку" или "Отправить". Пока сервер отвечает, нужно:

  1. Заблокировать повторный клик — иначе уйдут два одинаковых запроса.
  2. Показать, что идёт работа — текст "...", спиннер или пошаговые сообщения.
  3. Дождаться Promisefetch, navigator.share или любая async-функция.

В первой программе на React загрузка списка уже есть через useEffect. Здесь другой сценарий: действие по клику (onClick), а не автоматический запрос при монтировании.

Ниже три уровня — от ручного isLoading до потока значений из async function*.


Модель состояний кнопки

Кнопка с загрузкой — это маленькая state-machine:

  • idle — кнопка активна;
  • loading — действие выполняется и повторный запуск блокируется;
  • done/error — интерфейс возвращается в рабочее состояние.

Такой взгляд помогает писать надёжный UI без случайных дублей запросов и зависших disabled-состояний.


Базовый паттерн — useState и isLoading

Состояние загрузки хранят в useState. Пока операция выполняется, кнопку отключают (disabled) и меняют подпись (label).

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте используется конструкция import { useState } from 'react'; как точка входа сценария.
  • Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
  • JSX связывает состояние компонента и интерфейс: при изменении state React автоматически обновляет UI.
  • Используется асинхронное выполнение (async/await), поэтому интерфейс остаётся отзывчивым, пока операция не завершится.
  • Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
  • Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
ЭлементРоль
useState(false)флаг "операция в процессе"
if (isLoading) returnзащита от двойного клика до перерисовки
disabled={isLoading}браузер не шлёт повторный click
isLoading ? '...' : 'Create Todo'смена label на время ожидания
try/finallyisLoading сбрасывается и при ошибке

onClick={handleClick} — передаётся ссылка на функцию. Не пишите onClick={handleClick()}: тогда функция вызовется сразу при рендере. Подробнее — в частых ошибках React.

Promise и Event Loop — в асинхронности.


Обёртка LoadingButton

Один и тот же шаблон (isLoading, disabled, текст) повторяется в каждой форме. Его выносят в переиспользуемый компонент:

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте используется конструкция import { useState } from 'react'; как точка входа сценария.
  • Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
  • JSX связывает состояние компонента и интерфейс: при изменении state React автоматически обновляет UI.
  • Используется асинхронное выполнение (async/await), поэтому интерфейс остаётся отзывчивым, пока операция не завершится.
  • Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
  • Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
PropСмысл
childrenобычный label кнопки ("Create Todo", "Сохранить")
onClickфункция, возвращающая Promise (часто async () => { ... })

Компонент не знает, что именно делает onClick — только ждёт завершения Promise и управляет UI.


Сокращение через useAsyncCallback (библиотека)

Пакет react-async-hook инкапсулирует тот же паттерн:

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте используется конструкция import { useAsyncCallback } from 'react-async-hook'; как точка входа сценария.
  • Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
  • JSX связывает состояние компонента и интерфейс: при изменении state React автоматически обновляет UI.
  • Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
  • Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
ПолеРоль
executeзапуск обработчика по клику
loadingаналог isLoading

Это внешняя зависимость (npm install react-async-hook). Для учебных проектов достаточно своего LoadingButton на useState; библиотека удобна, когда таких кнопок много и нужны счётчики ошибок или отмена.


Поток значений — async function* и yield

Иногда одного "..." мало — длинная операция и пользователь должен видеть этапы — "Отправка", "Ожидание сервера", "Готово". Тогда обработчик делают асинхронным генератором: async function* () { yield ...; await ...; }.

Каждый yield отдаёт следующее значение label (строка или JSX). Между шагами — await wait(ms) (утилита из 32.md) или реальный fetch.

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте используется конструкция import { useState } from 'react'; как точка входа сценария.
  • Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
  • JSX связывает состояние компонента и интерфейс: при изменении state React автоматически обновляет UI.
  • Используется асинхронное выполнение (async/await), поэтому интерфейс остаётся отзывчивым, пока операция не завершится.
  • Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
  • Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.

Схема одного клика:

Разбор:

  • Диаграмма визуализирует последовательность шагов и роли участников в сценарии.
  • По стрелкам видно, где запускается действие, где идёт ожидание и где возвращается результат.
  • Такой формат удобно использовать для сверки бизнес-логики с фактической реализацией в коде.
  • После чтения схемы проще определить точки для логирования, обработки ошибок и улучшения UX.
КонструкцияСвязь с другими темами
async function*асинхронные генераторы
for await...ofобход AsyncIterator — потока значений
yieldпауза генератора и отдача очередного label
wait(ms)Promise вокруг setTimeoutПрактика

Теория генераторов и fetchPages — в Работа с объектами и прототипами; здесь тот же механизм, но значения идут в текст кнопки, а не в консоль.


label, children и JSX внутри кнопки

  • children — подпись в спокойном состоянии ("Click me", "Сохранить").
  • Во время загрузки label (state) может быть строкой или JSX — React перерисует содержимое <button>.
  • После finally подпись возвращают к children, чтобы кнопка снова была узнаваемой.

Если этапы только текстовые, достаточно строк в yield. JSX уместен, когда на кнопке нужен <code> или короткая разметка без отдельного модального окна.


Сравнение подходов

ПодходКогда использовать
isLoading вручнуюодна-две кнопки, учебный минимум
<LoadingButton onClick={async () => ...}>формы, мутации, POST через Node API
useAsyncCallbackмного async-кнопок, нужен готовый хук
async function* + yieldдлинные операции, пошаговый feedback на самой кнопке

Загрузка списка при открытии страницы по-прежнему через useEffect — см. Первая программа на React. Кнопка с isLoading — для действий пользователя.


Частые ошибки

СимптомПричина
Два одинаковых POSTнет disabled / isLoading, двойной клик
Кнопка "вечно" disabledзабыли setIsLoading(false) в finally
Запрос при загрузке страницыonClick={save()} вместо onClick={() => save()}
Генератор не обновляет текстне async function*, а обычная async function
yield без await между шагамиUI не успевает перерисоваться — добавьте wait(0) или реальный I/O

Связь с другими темами раздела

ТемаГде углубиться
useState, компоненты, onClickПервая программа на React, React - библиотека для пользовательских интерфейсов
Promise, async/awaitАсинхронное программирование в JavaScript
function*, async function*, for awaitРабота с объектами и прототипами
wait(ms)Практика
Async по клику без ReactКнопка "Поделиться" — DOM, события и Web Share API
POST к APIПервая программа на Node.js · Fullstack на JavaScript — API и фронтенд

Практика для продуктовых интерфейсов

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

  • показывает прогресс или этап операции;
  • блокирует соседние кнопки, если операция конфликтует;
  • отдаёт понятное сообщение об ошибке рядом с формой;
  • после успеха обновляет список и возвращает фокус пользователю.

Такой подход повышает предсказуемость интерфейса и снижает число повторных кликов.


Чек-лист качества для LoadingButton

ПроверкаОжидаемое поведение
Двойной кликвторой запуск блокируется
Ошибка APIisLoading сбрасывается в finally
Медленный запросподпись/индикатор сообщают о процессе
Успешный ответUI обновляется без перезагрузки страницы
Отмена операциисостояние кнопки корректно возвращается

Для сетевых операций соединяйте этот паттерн с AbortController и валидацией форм, чтобы получить полный цикл "проверка → отправка → отклик интерфейса".


Краткий итог

  1. useState(false) + isLoading — флаг загрузки; disabled={isLoading} блокирует повторный клик.
  2. onClick с async и await ждёт Promise (сеть, таймер, API).
  3. LoadingButton выносит повторяющийся шаблон; children — обычный label.
  4. async function* и yield дают поток значений для пошагового текста на кнопке; for await...of обходит этот поток в React.
  5. wait(ms) связывает паузы с Promise — удобно между yield в учебных примерах.

Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.