Кнопка с загрузкой — React, Promise и поток обновлений
См. также: Первая программа на React · Асинхронность · Генераторы · Утилита wait · Кнопка "Поделиться" (vanilla) · карта React · Web API на практике - примеры кода
Задача
Пользователь нажимает кнопку "Создать заметку" или "Отправить". Пока сервер отвечает, нужно:
- Заблокировать повторный клик — иначе уйдут два одинаковых запроса.
- Показать, что идёт работа — текст
"...", спиннер или пошаговые сообщения. - Дождаться Promise —
fetch,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/finally | isLoading сбрасывается и при ошибке |
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
| Проверка | Ожидаемое поведение |
|---|---|
| Двойной клик | второй запуск блокируется |
| Ошибка API | isLoading сбрасывается в finally |
| Медленный запрос | подпись/индикатор сообщают о процессе |
| Успешный ответ | UI обновляется без перезагрузки страницы |
| Отмена операции | состояние кнопки корректно возвращается |
Для сетевых операций соединяйте этот паттерн с AbortController и валидацией форм, чтобы получить полный цикл "проверка → отправка → отклик интерфейса".
Краткий итог
useState(false)+isLoading— флаг загрузки;disabled={isLoading}блокирует повторный клик.onClickсasyncиawaitждёт Promise (сеть, таймер, API).LoadingButtonвыносит повторяющийся шаблон;children— обычный label.async function*иyieldдают поток значений для пошагового текста на кнопке;for await...ofобходит этот поток в React.wait(ms)связывает паузы с Promise — удобно междуyieldв учебных примерах.
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.