> For the complete documentation index, see [llms.txt](https://max-frontend.gitbook.io/react-course-ru-v2/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://max-frontend.gitbook.io/react-course-ru-v2/rabota-s-formoi.md).

# Работа с формой

В данном уроке мы превратим наш *input* в форму добавления новости. Научимся работать с чекбоксами, disabled атрибутом кнопки и прочими стандартными для такой задачи вещами.

Результатом добавления новости, пока что, вновь, будет *alert* с текстом новости.

Переименуйте `<TestInput />` в `<Add />`, и рендерите в нем следующую форму: автор (*input*), текст новости (*textarea*), "я согласен с правилами" (*checkbox*), "показать alert" (*button*).

Попутно изменим названия классов, удалим автофокус, удалим лишние обработчики и переместим компонент `<Add />` перед заголовком "Новости".

Итого, заготовка для "добавления новости" будет выглядеть следующим образом:

```jsx
class Add extends React.Component {
  onBtnClickHandler = (e) => {
    e.preventDefault()
  }
  render() {
    return (
      <form className='add'>
        <input
          type='text'
          className='add__author'
          placeholder='Ваше имя'
        />
        <textarea
          className='add__text'
          placeholder='Текст новости'
        ></textarea>
        <label className='add__checkrule'>
          <input type='checkbox' /> Я согласен с правилами
        </label>
        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}>
          Показать alert
        </button>
      </form>
    )
  }
}
```

Если вы не против моего оформления, можете взять стили для компонента `<Add />`:

```css
.add {
  margin: 0 5px 5px 0;
  width: 210px;
  border: 1px dashed rgba(0, 89, 181, 0.82);
  padding: 5px;
}
.add__author, .add__text, .add__btn, .add__checkrule {
  display: block;
  margin: 0 0 5px 0;
  padding: 5px;
  width: 94%;
  border: 1px solid rgba(0, 89, 181, 0.82);
}
.add__checkrule {
  border: none;
  font-size: 12px;
}
.add__btn {
  box-sizing: content-box;
  color: #FFF;
  text-transform: uppercase;
  background: #007DDC;
}
.add__btn:disabled {
  background: #CCC;
  color: #999;
}
```

Так как мы близки к финалу, я бы хотел нагрузить вас работой.

**Задача**: сейчас инпут "ваше имя" и text area - просто "болванка". Нужно сделать их контролируемыми.

![add-news-task-1](/files/-LvLnSpeJH9v0hrqWqKP)

**Подсказка**:

* создайте state (начальное состояние)
* добавьте обработчики на изменение имени и текста новости
* в value элементов записывайте значение переменной из состояния

**Решение**: (код сейчас не идеальный, но понятный. Рефакторить будем в конце раздела)

```jsx
class Add extends React.Component {
  state = { // добавили начальное состояние
    name: '',
    text: '',
  }
  onBtnClickHandler = (e) => {
    e.preventDefault()
  }
  handleNameChange = (e) => { обработчик, в котором обновляем name
    this.setState({ name: e.currentTarget.value })
  }
  handleTextChange = (e) => { обработчик, в котором обновляем text
    this.setState({ text: e.currentTarget.value })
  }
  render() {
    const { name, text } = this.state // вытащили значения из стейта

    // добавили value для name и для textarea
    return (
      <form className='add'>
        <input
          type='text'
          onChange={this.handleNameChange}
          className='add__author'
          placeholder='Ваше имя'
          value={name}
        />
        <textarea
          onChange={this.handleTextChange}
          className='add__text'
          placeholder='Текст новости'
          value={text}
        ></textarea>
        <label className='add__checkrule'>
          <input type='checkbox' /> Я согласен с правилами
        </label>
        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}>
          Показать alert
        </button>
      </form>
    )
  }
}
```

Инпуты начали работать и у нас есть очень приятный бонус: имя и текст новости хранится в this.state. Удобно? Разумеется. Представьте, что мы будем делать валидацию формы. У нас в любой момент времени будут **актуальные значения** имени и текста новости! Все еще не в восторге? Немного терпения и мы доберемся до валидации...

Однако, для начала заставим работать связку чекбокс + кнопка отправки новости.

Давайте отключим кнопку "показать alert", если не отмечен чекбокс. Здесь есть 2 варианта - использовать *state* или не использовать. Для нашей задачи никаких проблем с производительностью не будет, если мы будем использовать *state*. Лазить в DOM (даже с помощью refs) в React-приложениях - не лучший вариант.

Разобьем задачу на этапы, необходимо:

* добавить значение в state для чекбокса (true/false);
* добавить атрибут *disabled* у кнопки равным значению из *state*;
* добавить функцию обработчик;

Попробуйте сами, либо посмотрите решение:

```jsx
class Add extends React.Component {
  state = {
    name: '',
    text: '',
    agree: false, // новое значение состояния - agree (булево)
  }
  onBtnClickHandler = (e) => {
    e.preventDefault()
  }
  handleNameChange = (e) => {
    this.setState({ name: e.currentTarget.value })
  }
  handleTextChange = (e) => {
    this.setState({ text: e.currentTarget.value })
  }
  handleCheckboxChange = (e) => { // обработчик кликов по чекбоксу
    // чтобы установить true/false считываем свойство checked
    this.setState({ agree: e.currentTarget.checked })
  }
  render() {
    const { name, text, agree } = this.state
    return (
      <form className='add'>
        <input
          type='text'
          onChange={this.handleNameChange}
          className='add__author'
          placeholder='Ваше имя'
          value={name}
        />
        <textarea
          onChange={this.handleTextChange}
          className='add__text'
          placeholder='Текст новости'
          value={text}
        ></textarea>
        <label className='add__checkrule'>
          <input type='checkbox' onChange={this.handleCheckboxChange} /> Я согласен с правилами
        </label>
        {/* кнопке добавили disabled равный (НЕ agree) */}
        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}
          disabled={!agree}>
          Показать alert
        </button>
      </form>
    )
  }
}
```

Так как по клику в чекбокс происходит изменение state, то вызывается перерисовка (метод render отработает). Значит, у нас всегда будет актуальное значение `agree` в `disabled` атрибуте.

![checkbox plus button](/files/-LvLnSpj7v1tXDSse7zC)

Мне нечего более здесь комментировать для тех, кто знает основы JavaScript. Для тех, кто не знает:

* [checked](https://learn.javascript.ru/attributes-and-custom-properties#%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B0-%D0%BA%D0%B0%D0%BA-%D0%B5%D1%81%D1%82%D1%8C-%D0%B8%D0%B7-%D0%B0%D1%82%D1%80%D0%B8%D0%B1%D1%83%D1%82%D0%B0-href) (Кантор);
* [Изменение: change, input, cut, copy, paste](https://learn.javascript.ru/events-change) (Кантор)
* `disabled = true` будет означать, что кнопка выключена. Кнопка должна быть выключена тогда, когда `agree = false` (то есть, чекбокс не отмечен), значит мы делаем отрицание (НЕ) с помощью знака восклицания;

Для добавления новости нам осталось сформировать код, который будет выводить в *alert* имя и текст новости. Я думаю, эта задача вам точно под силу.

![alert new news](/files/-LvLnSplkMtW0MeC7X-W)

**Решение**:

```jsx
...
onBtnClickHandler = (e) => {
  e.preventDefault()
  const { name, text } = this.state
  alert(name + '\n' + text) // \n = перенос строки
}
```

## Блокировка кнопки, если не все поля заполнены

Приехала обещанная валидация. Точнее, "*Валидация часть 1: Начало*".

Почему часть 1? Потому что, валидация форм и вообще работа с формой - одна из самых скрупулезных задач. Нужно показывать понятные сообщения об ошибках, подсвечивать неправильно заполненное поле, блокировать кнопку отправки, валидировать поля по определенным правилам и так далее.

В данный момент, мы добавим к условию (что чекбокс отмечен) только одно простое условие: поля name и text должны быть заполнены. И не пробелами.

Как бы решалась такая задача без react? Вероятно у нас была бы функция *validate*, которая вызывалась бы на каждое изменение в проверяемых полях. Нужно было бы генерировать и прослушивать событие...

Думаю, вы поняли намек. Здесь без *state* не обойтись, и это точно то место, где удобнее использовать именно **состояние**, а не refs.

Попробуйте сами, а потом сверьтесь с решением.

**Задача**: если в поле "имя" или "текст" не введено ничего (либо пробелы) - кнопка "показать alert" должна быть недоступной.

![add news validate](/files/-LvLnSpn2ub7MrspGCBk)

Подсказка **#1**: для удаления пробелов используйте стандартный метод [trim()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)

Подсказка **#2**: вам потребуется в атрибут *disabled* передавать результат работы функции *validate* (которую нужно создать в качестве метода компонента, чтобы был доступ к *this.state*). Пример:

```jsx
validate = () => {
  // какие то условия
  // возвращает true или false
}
...
<button disabled={this.validate()} ... >
...
```

**Решение**:

```jsx
class Add extends React.Component {
  state = {
    name: '',
    text: '',
    agree: false,
  }
  ...
  validate = () => {
    const { name, text, agree } = this.state
    if (name.trim() && text.trim() && agree) {
      return true
    }
    return false
  }
  render() {
    const { name, text, agree } = this.state
    return (
      <form className='add'>
        ...
        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}
          disabled={!this.validate()}>
          Показать alert
              </button>
      </form>
    )
  }
}
```

Ну как? Не очень весело? Если да - значит у вас проблемы с основами JavaScript и вам нужно их подтягивать. А если в целом порядочек - я вас поздравляю, мы почти у цели.

## Порефакторим копипасту

Есть проблемка:

```jsx
handleNameChange = (e) => {
  this.setState({ name: e.currentTarget.value })
}
handleTextChange = (e) => {
  this.setState({ text: e.currentTarget.value })
}
```

Очень похожие методы. Можно ли их унифицировать? Конечно.

* необходимо добавить вычисляемое значение ключа;
* понять откуда будем считывать ключ;

Ключ будем считывать из id элемента. Идея такова: в id записываем такую же строку, что и значение ключа в state, то есть для name - инпуту дадим `id='name'`, а для textarea - `id='text'`

Готовы?

```jsx
class Add extends React.Component {
  ...
  handleChange = (e) => {
    const { id, value } = e.currentTarget
    this.setState({ [id]: e.currentTarget.value })
  }
  ...
  render() {
    const { name, text, agree } = this.state
    return (
      <form className='add'>
        <input
          id='name'
          type='text'
          onChange={this.handleChange}
          className='add__author'
          placeholder='Ваше имя'
          value={name}
        />
        <textarea
          id='text'
          onChange={this.handleChange}
          className='add__text'
          placeholder='Текст новости'
          value={text}
        ></textarea>
        ...
      </form>
    )
  }
}
```

Из `e.currentTarget` мы можем считать `id` и `value`. Далее записываем в стейт по нужному ключу - значение.

Вычисляемое значение ключа - это одна из самых больных тем новичков. Внимательно читаем (и перечитываем) урок из учебника Кантора: [Объекты как ассоциативные массивы](https://learn.javascript.ru/object)

**Итого**: мы научились работать с формой. Положили начало досерверной валидации. Поняли и осознали, что знание основ JavaScript - это невероятный "буст" в изучении React. Да, без основ можно читать туториалы и выполнять задачи, но поверьте, если вы сначала подтянете основы, то React станет вам другом гораздо быстрее.

[Исходный код](https://github.com/maxfarseer/react-course-ru-v2/tree/chp11-work-with-form) на данный момент.
