Создание React форм в 2020 году

Привет, Хабр. Представляю вам перевод статьи «Creating forms in React in 2020» автора Kristofer Selbekk

image


Поля ввода. Текстовые области. Радиокнопки и чекбоксы. Вот некоторые из основных точек взаимодействия, которые мы, как разработчики, имеем с нашими пользователями. Мы размещаем их на сайте, пользователи их заполняют и если нам повезёт, то заполненные формы приходят к нам без ошибок валидации.

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

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

Помните о правилах хорошего тона


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

Обработка форм с использованием State Hook


Для начала давайте посмотрим, как я обычно работаю с состоянием формы. Я сохраняю все поля как отдельные элементы состояния и обновляю их все по отдельности, что выглядит примерно так:

function LoginForm() {
  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    api.login(email, password);
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor='email'>Email</label>
        <input type='email' id='email' value={email} onChange={(e) => setEmail(e.target.value)} />
      </div>
      <div>
        <label htmlFor='password'>Password</label>
        <input type='password' id='password' value={password} onChange={(e) => setPassword(e.target.value)} />
      </div>
    </form>
  );
}

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

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

Создание пользовательского хука


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

const useFormField = (initialValue: string = '') => {
  const [value, setValue] = React.useState(initialValue);
  const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), []);
  return { value, onChange };
};

export function LoginForm() {
  const emailField = useFormField();
  const passwordField = useFormField();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    api.login(emailField.value, passwordField.value);
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor='email'>Email</label>
        <input type='email' id='email' {...emailField} />
      </div>
      <div>
        <label htmlFor='password'>Password</label>
        <input type='password' id='password' {...passwordField} />
      </div>
    </form>
  );
}

Мы создаем пользовательский хук useFormField, который создает для нас обработчик события onChange, а также сохраняет значение в состояние формы. В этом случае мы можем использовать наш хук со всеми полями формы.

Обработка большого количества полей


Недостатком этого подхода является то, что он плохо масштабируется по мере роста вашей формы. Для небольших форм, таких как login, это, вероятно, хорошо, но когда вы создаете формы профиля пользователя, вам может понадобиться запросить много информации! Должны ли мы вызывать наш хук снова и снова?

Всякий раз, когда я сталкиваюсь с подобной ситуацией, я склоняюсь к написанию пользовательского хука, который хранит все мои формы в одном большом фрагменте. Это может выглядеть так:

export function LoginForm() {
  const { formFields, createChangeHandler } = useFormFields({
    email: '',
    password: '',
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    api.login(formFields.email, formFields.password);
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor='email'>Email</label>
        <input type='email' id='email' value={formFields.email} onChange={createChangeHandler('email')} />
      </div>
      <div>
        <label htmlFor='password'>Password</label>
        <input type='password' id='password' value={formFields.password} onChange={createChangeHandler('password')} />
      </div>
    </form>
  );
}

С помощью хука useFormFields мы можем продолжать добавлять поля, не добавляя сложности нашему компоненту. Мы можем получить доступ ко всем состояниям формы в одном месте, а наш код выглядит аккуратно и лаконично. Конечно, вам, вероятно, понадобится использовать setState для некоторых ситуаций, но для большинства форм приведённый приём подойдёт просто отлично.

Альтернативный подход


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

Вот та же форма, но позволяющая браузеру обрабатывать состояние формы:

export function LoginForm() {
  const handleSubmit = (e: React.FormEvent) => {
	e.preventDefault();
	const formData = new FormData(e.target as HTMLFormElement);
	api.login(formData.get('email'), formData.get('password'));
  };
  return (
	<form onSubmit={handleSubmit}>
  	<div>
    	<label htmlFor="email">Email</label>
    	<input
      	type="email"
      	id="email"
      	name="email"
    	/>
  	</div>
  	<div>
    	<label htmlFor="password">Password</label>
    	<input
      	type="password"
      	id="password"
      	name="password"
    	/>
  	</div>
  	<button>Log in</button>
	</form>
  );
}

Посмотрите как просто! Не использовано ни одного хука, нет установки начального значения, отсутствует обработчик onChange. Самое приятное то, что код все еще работает как и прежде — но как?

Вы могли заметить, что мы делаем что-то немного другое в функции handleSubmit. Мы используем встроенный browser API под названием FormData. FormData — это удобный (и хорошо поддерживаемый) способ получения значений из наших полей ввода!

Мы получаем ссылку на форму в DOM через атрибут target события submit и создаем новый экземпляр класса FormData. Теперь мы можем получить все поля по их атрибуту name, вызвав formData.get («имя поля ввода»).

Таким образом, вам никогда не нужно явно обрабатывать состояние формы. Если вам нужны значения по умолчанию (например, если вы изменяете начальные значения полей из базы данных или локального хранилища), React предоставляет вам удобную опцию defaultValue для этого.

Когда лучше использовать каждый подход


Поскольку формы являются неотъемлемой частью большинства веб-приложений, важно знать, как с ними обращаться. И React предоставляет вам множество способов сделать это.
Для простых форм, которые не требуют тщательной проверки (или которые могут полагаться на элементы управления проверкой формы HTML5), я предлагаю вам просто использовать встроенную обработку состояний, которую DOM предоставляет нам по умолчанию. Есть довольно много вещей, которые вы не можете сделать (например, программно изменить входные значения или проверка в реальном времени), но в самых простых случаях (например, поле поиска или поле входа в систему, как указано выше), вам, вероятно, подойдёт наш альтернативный подход с использованием browser API.

Когда вы выполняете пользовательскую проверку или вам нужен доступ к некоторым данным формы перед её отправкой, вам нужно явно обработать состояние с помощью контролируемых компонентов. Вы можете использовать обычные useStateHooks или создать пользовательский хук, чтобы немного упростить ваш код.

Стоит отметить, что разработчики React рекомендуют использовать контролируемые компоненты (явно обрабатывать состояние компонента) в большинстве случаев — поскольку этот подход дает вам большую гибкость в дальнейшем. На мой взгляд, разработчики часто жертвуют простотой ради гибкости, которая им зачастую не нужна.

Какой бы подход вы не решили использовать, обработка форм в React никогда не была такой простой, как сегодня. Вы можете позволить браузеру обрабатывать простые формы, в то же время явно обрабатывая состояние форм, когда того требует ситуация. Надеюсь, приведённые способы, будут вам полезны и помогут решить задачу, сократив количество строк в вашем коде.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 12

    0
    Как бы вот — codesandbox.io/s/cool-leaf-xnh59?file=/src/App.tsx

          <Input model={state.form.firstName} />
          <Input model={state.form.lastName} />
          <Select model={state.form.gender}>
            <option value="male">Male</option>
            <option value="female">Female</option>
          </Select>
    
      +2

      Или просто берём Formik и забываем о практически всей боли при работе с формами.

        0
        Ну нет, это совсем стремно.
          +1

          Ничего стрёмного там нет, а вот жизнь очень сильно упрощает.
          С простыми полями (input, select…) formik берёт большую часть работы на себя из коробки (в большинстве случаев даже не надо обработчики событий развешивать, достаточно name='email' для связи хранилищем значений).
          Состояние формы он тоже берёт на себя (сабмитится ли форма или нет в данный момент, например).
          Поддержка валидации с помощью yup.js тоже из коробки – составляешь схему, скармливаешь её в Formik, и валидация сразу работает.


          Так что Formik прекрасен, если уметь его готовить, а научиться его готовить не так уж сложно.

            +1
            Используйте на здоровье, если нравиться.
            Но я например ни за что такое использовать не буду)
            Можно сделать гораздо удобнее и гибче.
        0
        Есть лучше альтернатива, чем Formik — это react-hook-form.
        0
        Не совсем понятно, для чего использовать FormData.
        Данные можно брать непосредственно с формы:
        e.target.elements.email.value
        e.target.elements.password.value
          0

          Я как-то ожидал в 2020 увидеть что-то про валидацию и динамику, а не самописный "предпочтительный" велосипед // Наверное, год все же нужно немного другой указать

            –1
            Сразу видно уровень когда говорят в негативном контексте «самописный», «велосипед». Вы не разработчик, а прикручиватель, вы ошиблись специализацией, вам надо идти в 1C Bitrix или Wordpress, вот там вам будет по кайфу, куча готовых модулей, вообще программировать не надо, нажимай галочки и вперед.
              +1

              Не вижу ничего негативного в термине "велосипед". Думаю для посетителей хабра вполне привычно называть самописные реализации существующего функционала велосипедами. Очень жаль, если это настолько задевает Ваши чувства.
              Акцент в комментарии был на том, что главная проблема работы с формами на данный момент — это не то, как сохранить в стейт больше одного поля (эта задача вряд-ли поставит кого-то в тупик), а именно валидация, отправка, зависимости между полями, динамика и т.д. Для решения этих задач существуют комплексные популярные решения (вроде упомянутого пару комментариев назад Formik'а, JSON schema и т.п.). Предложенные в статье реализации подобные задача никак не решают. Также они не являются инновационными, общепринятыми или хотябы имеющими дополнительную полезную нагрузку кроме простого сохранения значений полей формы в состоянии компонента. Метод с неконтролируемой формой и вовсе не самый рекомендуемый (по крайней мере для React). Именно по этой причине я считаю вполне корректным называть данные реализации "велосипедами" и в корне не согласен с окончанием "… в 2020 году" в заголовке статьи. На мой взгляд больше подходит окончание "… для начинающих". Такие дела.

                0
                Не вижу ничего негативного в термине «велосипед»

                А я вижу, ещё как.
                Во первых под этим термином в подавляющем большинстве подаразумевается следующее — «Ты написал то, что уже за тебя кто-то другой написал, и тем более полюбому твоя реализация будет хуже других, вот ты дэбил».

                В таком то контексте ничего негативного?
                а не самописный «предпочтительный» велосипед


                именно валидация, отправка, зависимости между полями, динамика и т.д.

                Вот именно эти вещи пишутся самим разработчиком(не прикручивателем, он с этим не справится или справится крайне плохо и не удобно) 1 раз и используются на протяжения всего проекта. В чем проблема то??? Так обломно выделить драгоценный час времени? А думать по логике, если я что-то пишу сам, значит я изобретаю велосипед и это говно, ну что я могу сказать, wordpress, bitrix и т.п. там думать не надо и писать ничего самому не надо, выбирай виджеты, жми на галочки и готово.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое