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

*ОБНОВЛЕНИЕ 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](https://2032922960-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvLmy8nmqH3uUpCfPAD%2F-LvLmydxF5hwFbPJLrLG%2F-LvLnMrDbcva0SDX9D_7%2Fcheckbox_state.jpg?generation=1575561865891735\&alt=media)

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

В данном случае я за второй вариант. Для меня большим злом является необходимость работать с 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) на данный момент.
