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

*ОБНОВЛЕНИЕ 2018: в учебнике хорошая теория, но ему уже два года. Проверяйте версии пакетов. За выходом нового учебника можно следить в* [*telegram канале*](http://bit.ly/2tS7QUC) *или* [*twitter*](http://bit.ly/2HADDLE)

*На* [*канале*](http://bit.ly/2tS7QUC) *так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы,* [*присоединяйтесь*](http://bit.ly/2tS7QUC)*!*

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

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

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

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

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

```javascript
var Add = React.createClass({
  componentDidMount: function() {
    ReactDOM.findDOMNode(this.refs.author).focus();
  },
  onBtnClickHandler: function(e) {
    e.preventDefault();
  },
  render: function() {
    return (
      <form className='add cf'>
        <input
          type='text'
          className='add__author'
          defaultValue=''
          placeholder='Ваше имя'
          ref='author'
        />
        <textarea
          className='add__text'
          defaultValue=''
          placeholder='Текст новости'
          ref='text'
        ></textarea>
        <label className='add__checkrule'>
          <input type='checkbox' defaultChecked={false} ref='checkrule' />Я согласен с правилами
        </label>
        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}
          ref='alert_button'>
          Показать 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;
}
```

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

**Решение 1**: без использования *state*.

Добавьте инпуту обработчик *onChange*, и добавьте функцию обработчик.

```javascript
...
onCheckRuleClick: function(e) {
  ReactDOM.findDOMNode(this.refs.alert_button).disabled = !e.target.checked;
},
...
<label className='add__checkrule'>
  <input type='checkbox' defaultChecked={false} ref='checkrule' onChange={this.onCheckRuleClick}/>Я согласен с правилами
</label>
...
```

Проверьте в браузере. При первой загрузке, кнопка активна, хотя чекбокс не стоит. Хех, решается на *HTML* ;) Добавьте атрибут *disabled* кнопке.

```javascript
<button
  className='add__btn'
  onClick={this.onBtnClickHandler}
  ref='alert_button'
  disabled>
  Показать alert
</button>
```

**Решение 2**: с использованием *state*

* добавьте *getInitialState* компоненту `<Add />`;
* удалите *defaultChecked* из инпута;
* сделайте атрибут *disabled* у кнопки равным значению из *state*;
* измените функцию обработчик;

Приведу полный код компонента `<Add />`

```javascript
var Add = React.createClass({
  getInitialState: function() { //устанавливаем начальное состояние (state)
    return {
      btnIsDisabled: true
    };
  },
  componentDidMount: function() {
    ReactDOM.findDOMNode(this.refs.author).focus();
  },
  onBtnClickHandler: function(e) {
    e.preventDefault();
  },
  onCheckRuleClick: function(e) {
    this.setState({btnIsDisabled: !this.state.btnIsDisabled}); //устанавливаем значение в state
  },
  render: function() {
    return (
      <form className='add cf'>
        <input
          type='text'
          className='add__author'
          defaultValue=''
          placeholder='Ваше имя'
          ref='author'
        />
        <textarea
          className='add__text'
          defaultValue=''
          placeholder='Текст новости'
          ref='text'
        ></textarea>
        <label className='add__checkrule'>
          <input type='checkbox' ref='checkrule' onChange={this.onCheckRuleClick}/>Я согласен с правилами
        </label>

        {/* берем значение для disabled атрибута из state */}
        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}
          ref='alert_button'
          disabled={this.state.btnIsDisabled}
          >
          Показать alert
        </button>
      </form>
    );
  }
});
```

![checkbox state screen](/files/-LvLnMrDbcva0SDX9D_7)

Какое решение выбрать?

В данном случае я за второй вариант. Для меня большим злом является необходимость работать с DOM, чем "рендер" компонента `<Add />` на каждый клик по чекбоксу. В данный момент я могу гарантировать, что никакой проблемы с производительностью не будет. Но заметьте, для input'a я все же оставил по прежнему схему работы через DOM. Путано? Я руководствовался тем, что клик по чекбоксу - событие не частое, а возможно единичное. `<input />` и `<textarea></textarea>` - более вредные в моем понимании. События *onChange* происходят в них слишком часто.

Работайте как вам нравится, просто знайте, что есть два варианта.

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

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

Достаточно всего лишь подкорректировать функцию *onBtnClickHandler*

```javascript
onBtnClickHandler: function(e) {
  e.preventDefault();
  var author = ReactDOM.findDOMNode(this.refs.author).value;
  var text = ReactDOM.findDOMNode(this.refs.text).value;
  alert(author + '\n' + text);
},
```

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

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

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

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

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

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

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

Подсказка **#2**: вам потребуется больше переменных, так же лучше переименовать переменную *btnIsDisabled*...

```javascript
...
getInitialState: function() { //устанавливаем начальное состояние (state)
  return {
    agreeNotChecked: true,
    authorIsEmpty: true,
    textIsEmpty: true
  };
},
...
```

Подсказка **#3**: объявите обработчики на *onChange*

```javascript
...
onAuthorChange: function(e) {
  if (e.target.value.trim().length > 0) {
    this.setState({authorIsEmpty: false})
  } else {
    this.setState({authorIsEmpty: true})
  }
},
onTextChange: function(e) {
  if (e.target.value.trim().length > 0) {
    this.setState({textIsEmpty: false})
  } else {
    this.setState({textIsEmpty: true})
  }
},
...
<input
  type='text'
  className='add__author'
  onChange={this.onAuthorChange}
  placeholder='Ваше имя'
  ref='author'
/>
<textarea
  className='add__text'
  onChange={this.onTextChange}
  placeholder='Текст новости'
  ref='text'
></textarea>
...
```

Если вас смущает дублирование кода - спокойствие, позже порефакторим.

Подсказка **#4**: у атрибута *disabled* проверяйте выполнение условия: *если хотя бы одно из свойств состояния (agreeNotChecked, authorIsEmpty, textIsEmpty) имеет значение true - кнопка выключается*.

```javascript
<button
  className='add__btn'
  onClick={this.onBtnClickHandler}
  ref='alert_button'
  disabled={agreeNotChecked || authorIsEmpty || textIsEmpty}
  >
  Показать alert
</button>
```

**Решение**: код компонента `<Add />` полностью:

```javascript
var Add = React.createClass({
  getInitialState: function() { //устанавливаем начальное состояние (state)
    return {
      agreeNotChecked: true,
      authorIsEmpty: true,
      textIsEmpty: true
    };
  },
  componentDidMount: function() {
    ReactDOM.findDOMNode(this.refs.author).focus();
  },
  onBtnClickHandler: function(e) {
    e.preventDefault();
    var author = ReactDOM.findDOMNode(this.refs.author).value;
    var text = ReactDOM.findDOMNode(this.refs.text).value;
    alert(author + '\n' + text);
  },
  onCheckRuleClick: function(e) {
    this.setState({agreeNotChecked: !this.state.agreeNotChecked}); //устанавливаем значение в state
  },
  onAuthorChange: function(e) {
    if (e.target.value.trim().length > 0) {
      this.setState({authorIsEmpty: false})
    } else {
      this.setState({authorIsEmpty: true})
    }
  },
  onTextChange: function(e) {
    if (e.target.value.trim().length > 0) {
      this.setState({textIsEmpty: false})
    } else {
      this.setState({textIsEmpty: true})
    }
  },
  render: function() {
    var agreeNotChecked = this.state.agreeNotChecked,
        authorIsEmpty = this.state.authorIsEmpty,
        textIsEmpty = this.state.textIsEmpty;
    return (
      <form className='add cf'>
        <input
          type='text'
          className='add__author'
          onChange={this.onAuthorChange}
          placeholder='Ваше имя'
          ref='author'
        />
        <textarea
          className='add__text'
          onChange={this.onTextChange}
          placeholder='Текст новости'
          ref='text'
        ></textarea>
        <label className='add__checkrule'>
          <input type='checkbox' ref='checkrule' onChange={this.onCheckRuleClick}/>Я согласен с правилами
        </label>

        <button
          className='add__btn'
          onClick={this.onBtnClickHandler}
          ref='alert_button'
          disabled={agreeNotChecked || authorIsEmpty || textIsEmpty}
          >
          Показать alert
        </button>
      </form>
    );
  }
});
```

### Избавляемся от дублирования кода

Дублирование кода в функциях *onAuthorChange* и *onTextChange* - это плохо. Хорошо было бы создать одну функцию, которая принимала бы аргумент - *fieldName* и изменяла бы соответствующую переменную в *state* согласно нашей логике.

Как передать аргумент в функцию? Что насчет такого варианта:

```javascript
<input
  type='text'
  className='add__author'
  onChange={this.onFieldChange('имя_переменной')}
  placeholder='Ваше имя'
  ref='author'
/>
```

Откровенно плохой вариант. Функция сразу же выполнится - так как указаны ().

Нам нужно передать именно функцию, а не **результат ее выполнения**. На помощь приходит метод [bind()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_objects/Function/bind).

Верной строкой для *onChange* в таком случае будет:

Для input'a:

```javascript
onChange={this.onFieldChange.bind(this, 'authorIsEmpty')}
```

Для textarea:

```javascript
onChange={this.onFieldChange.bind(this, 'textIsEmpty')}
```

Напишем саму функцию *onFieldChange*:

```javascript
...
onFieldChange: function(fieldName, e) {
  if (e.target.value.trim().length > 0) {
    this.setState({[''+fieldName]:false})
  } else {
    this.setState({[''+fieldName]:true})
  }
},
...
```

Я надеюсь, не возникает вопросов, откуда взялся аргумент *fieldName*, почему аргумент '*e*' теперь второй? (если что, это все *bind* виноват, он "прокинул" нашу переменную в функцию первым аргументом, можно было прокинуть еще...)

Так как в переменной у нас строка, мы не можем передать ее напрямую в *setState* в качестве названия поля объекта.

Возможно вам покажется более читаемым такой вариант:

```javascript
...
onFieldChange: function(fieldName, e) {
  var next = {};
  if (e.target.value.trim().length > 0) {
    next[fieldName] = false;
    this.setState(next);
  } else {
    next[fieldName] = true;
    this.setState(next);
  }
},
...
```

Разницы между ними нет, в этом "противостоянии" я выбираю первый вариант.

**Итого**: мы разобрали пару стандартных способов блокировать нажатие кнопки на форме. Мы опять не взяли событие *onSubmit* для формы и ограничились лишь событием *onClick*.

По традиции - [исходный код](https://github.com/maxfarseer/react-ru-tutorial/tree/work_with_form) на данный момент.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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/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.
