Неконтролируемые формы

16 июня 2026
Автор

Олег Марков

Неконтролируемые формы в React

Введение

Существуют два подхода к работе с формами в React: контролируемые компоненты, где React-состояние является источником истины, и неконтролируемые компоненты, где DOM сам управляет данными.

В неконтролируемых формах вы не привязываете value к состоянию и не слушаете каждое нажатие клавиши. Вместо этого значения полей читаются напрямую из DOM — обычно только при отправке формы. Это делает код проще и может улучшить производительность для больших форм.

Несмотря на то, что React-документация рекомендует контролируемые компоненты, неконтролируемые формы имеют свои законные области применения: загрузка файлов, интеграция со сторонними DOM-библиотеками, простые формы с минимальной валидацией.

Основной принцип: useRef

Главный инструмент для работы с неконтролируемыми формами — хук useRef. Он создаёт объект ref, который можно привязать к DOM-элементу и затем читать его текущее значение.

import React, { useRef } from 'react';

function SimpleUncontrolledForm() {
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    // Читаем значение напрямую из DOM
    console.log('Значение:', inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        ref={inputRef}
        // Нет атрибута value — DOM управляет значением
      />
      <button type="submit">Отправить</button>
    </form>
  );
}

defaultValue vs value

В неконтролируемых компонентах используйте defaultValue вместо value для задания начального значения:

function UncontrolledInput() {
  const inputRef = useRef(null);

  return (
    <div>
      {/* value — контролируемый (React управляет) */}
      <input value="Иван" onChange={() => {}} />

      {/* defaultValue — неконтролируемый (DOM управляет, начальное значение задано) */}
      <input defaultValue="Иван" ref={inputRef} />

      {/* Без defaultValue — начальное значение пустое */}
      <input ref={inputRef} />
    </div>
  );
}

Аналогично для других элементов:

  • <textarea defaultValue="..."> вместо value
  • <select defaultValue="..."> вместо value
  • <input type="checkbox" defaultChecked={true}> вместо checked

Простая форма входа

function LoginForm() {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();

    const email = emailRef.current.value;
    const password = passwordRef.current.value;

    // Простая валидация при отправке
    if (!email || !password) {
      alert('Заполните все поля');
      return;
    }

    console.log('Вход:', { email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          id="email"
          type="email"
          ref={emailRef}
          defaultValue=""
        />
      </div>

      <div>
        <label htmlFor="password">Пароль:</label>
        <input
          id="password"
          type="password"
          ref={passwordRef}
        />
      </div>

      <button type="submit">Войти</button>
    </form>
  );
}

FormData API

Мощный способ работы с неконтролируемыми формами — использование нативного FormData. Это особенно удобно для форм с большим количеством полей:

function RegistrationForm() {
  const handleSubmit = (e) => {
    e.preventDefault();

    // FormData автоматически собирает все поля с атрибутом name
    const formData = new FormData(e.target);

    // Получить отдельное поле
    const name = formData.get('name');
    const email = formData.get('email');

    // Преобразовать в обычный объект
    const data = Object.fromEntries(formData.entries());
    console.log(data);
    // { name: 'Иван', email: 'ivan@example.com', role: 'user' }

    // Получить все значения одного поля (для чекбоксов/множественного выбора)
    const skills = formData.getAll('skills');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" type="text" defaultValue="" placeholder="Имя" />
      <input name="email" type="email" defaultValue="" placeholder="Email" />

      <select name="role" defaultValue="user">
        <option value="user">Пользователь</option>
        <option value="admin">Администратор</option>
      </select>

      <fieldset>
        <legend>Навыки</legend>
        <label><input type="checkbox" name="skills" value="react" /> React</label>
        <label><input type="checkbox" name="skills" value="typescript" /> TypeScript</label>
        <label><input type="checkbox" name="skills" value="nodejs" /> Node.js</label>
      </fieldset>

      <button type="submit">Зарегистрироваться</button>
    </form>
  );
}

Работа с файлами

Загрузка файлов — область, где неконтролируемые компоненты практически незаменимы, потому что значение <input type="file"> нельзя контролировать программно по соображениям безопасности:

function FileUpload() {
  const fileInputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();

    const files = fileInputRef.current.files;
    if (!files.length) {
      alert('Выберите файл');
      return;
    }

    const file = files[0];
    console.log('Файл:', {
      name: file.name,
      size: file.size,
      type: file.type,
    });

    // Отправка на сервер
    const formData = new FormData();
    formData.append('file', file);
    fetch('/api/upload', { method: 'POST', body: formData });
  };

  const handleClear = () => {
    // Сбросить выбранный файл
    fileInputRef.current.value = '';
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="file"
        ref={fileInputRef}
        accept="image/*,.pdf"
        multiple
      />
      <button type="submit">Загрузить</button>
      <button type="button" onClick={handleClear}>Очистить</button>
    </form>
  );
}

Превью изображения перед загрузкой

function ImageUpload() {
  const fileInputRef = useRef(null);
  const [preview, setPreview] = useState(null);

  const handleFileChange = (e) => {
    const file = e.target.files[0];
    if (!file) return;

    const reader = new FileReader();
    reader.onloadend = () => setPreview(reader.result);
    reader.readAsDataURL(file);
  };

  return (
    <div>
      <input
        type="file"
        ref={fileInputRef}
        accept="image/*"
        onChange={handleFileChange}
      />
      {preview && (
        <img src={preview} alt="Превью" style={{ maxWidth: '200px' }} />
      )}
    </div>
  );
}

Программный сброс формы

Сброс неконтролируемой формы:

function ResetableForm() {
  const formRef = useRef(null);
  const nameRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const name = nameRef.current.value;
    console.log('Submit:', name);

    // Способ 1: Нативный сброс через form.reset()
    formRef.current.reset();

    // Способ 2: Сбросить конкретное поле
    // nameRef.current.value = '';
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input ref={nameRef} name="name" defaultValue="" placeholder="Имя" />
      <button type="submit">Отправить</button>
      <button type="reset">Сбросить</button>
    </form>
  );
}

Фокус и управление DOM

Одно из главных преимуществ useRef — программное управление DOM-элементами:

function AutoFocusForm() {
  const firstInputRef = useRef(null);

  // Автофокус при монтировании компонента
  useEffect(() => {
    firstInputRef.current?.focus();
  }, []);

  const handleError = () => {
    // Переместить фокус на проблемное поле
    firstInputRef.current?.focus();
    firstInputRef.current?.select(); // Выделить весь текст
  };

  return (
    <form>
      <input
        ref={firstInputRef}
        type="text"
        placeholder="Введите имя"
      />
    </form>
  );
}

Интеграция со сторонними библиотеками

Неконтролируемые компоненты необходимы при интеграции с DOM-библиотеками (jQuery-плагины, редакторы типа Quill, карты):

import { useRef, useEffect } from 'react';

function QuillEditor({ onChange }) {
  const editorRef = useRef(null);
  const quillRef = useRef(null);

  useEffect(() => {
    // Инициализируем Quill на DOM-элементе
    quillRef.current = new Quill(editorRef.current, {
      theme: 'snow',
      modules: { toolbar: true },
    });

    // Передаём изменения наружу
    quillRef.current.on('text-change', () => {
      onChange(quillRef.current.root.innerHTML);
    });

    return () => {
      // Очистка при размонтировании
      quillRef.current = null;
    };
  }, []);

  return <div ref={editorRef} style={{ height: '200px' }} />;
}

Частичная неконтролируемость

Иногда удобно смешивать подходы — управлять одними полями через state, а другими через refs:

function HybridForm() {
  // Контролируемое поле для реактивной валидации
  const [email, setEmail] = useState('');
  const [emailError, setEmailError] = useState('');

  // Неконтролируемые поля — не нуждаются в реактивности
  const nameRef = useRef(null);
  const phoneRef = useRef(null);
  const fileRef = useRef(null);

  const handleEmailChange = (e) => {
    const value = e.target.value;
    setEmail(value);
    setEmailError(!/\S+@\S+\.\S+/.test(value) ? 'Некорректный email' : '');
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = {
      name: nameRef.current.value,
      email,
      phone: phoneRef.current.value,
      file: fileRef.current.files[0],
    };
    console.log('Submit:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* Неконтролируемые поля */}
      <input ref={nameRef} name="name" placeholder="Имя" />
      <input ref={phoneRef} name="phone" type="tel" placeholder="Телефон" />
      <input ref={fileRef} type="file" />

      {/* Контролируемое поле с валидацией */}
      <input
        type="email"
        value={email}
        onChange={handleEmailChange}
        placeholder="Email"
      />
      {emailError && <span>{emailError}</span>}

      <button type="submit">Отправить</button>
    </form>
  );
}

Когда использовать неконтролируемые формы

Подходящие случаи:

  • Загрузка файлов (<input type="file">)
  • Интеграция со сторонними DOM-библиотеками
  • Простые формы поиска или подписки без сложной валидации
  • Миграция legacy-кода с jQuery
  • Большие формы с сотнями полей, где каждый ре-рендер критичен

Когда лучше контролируемые формы:

  • Нужна валидация в реальном времени
  • Поля зависят друг от друга
  • Нужно программно менять значения полей
  • Используете библиотеки типа Formik или React Hook Form

Производительность

Неконтролируемые формы не вызывают ре-рендер при каждом нажатии клавиши, что может быть важно для очень больших форм:

// Контролируемая — ре-рендер при каждом нажатии
function ControlledInput() {
  const [value, setValue] = useState(''); // Каждое изменение = ре-рендер
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

// Неконтролируемая — ре-рендер только при submit
function UncontrolledInput() {
  const ref = useRef(null); // Нет ре-рендера при изменении
  return <input ref={ref} />;
}

Однако в большинстве реальных приложений эта разница не ощутима. React достаточно эффективно обрабатывает частые ре-рендеры одного поля ввода.

Заключение

Неконтролируемые формы — важный инструмент в арсенале React-разработчика. Они особенно полезны для работы с файлами, интеграции со сторонними библиотеками и простых сценариев, где не нужна реактивная валидация.

Ключевые выводы:

  • Используйте useRef для прямого доступа к DOM-элементам
  • Применяйте defaultValue/defaultChecked вместо value/checked
  • FormData API позволяет удобно собирать данные большой формы
  • form.reset() сбрасывает все поля формы к начальным значениям
  • <input type="file"> — неконтролируемый по природе

Выбирайте подход в зависимости от требований: для сложных форм с валидацией — контролируемые компоненты (или React Hook Form), для простых случаев и файлов — неконтролируемые.

Маски вводаСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать React DOM в проектеКак использовать элементы в ReactЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий