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

В данном уроке мы превратим наш *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) на данный момент.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://max-frontend.gitbook.io/react-course-ru-v2/rabota-s-formoi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
