React и формы (Forms)

HTML элементы формы работают в React немного не так, как другие элементы DOM, потому как они хранят некоторое внутреннее состояние. Например, эта форма в виде обычного HTML принимает одно имя:

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

Эта форма имеет поведение по умолчанию  присущее HTML-формам — отправка данных на новую страницу, при нажатии на кнопку пользователем. Если вы планировали такое поведение, то оно так и сработает. Но в большинстве случаев, удобно иметь функцию JavaScript, которая обрабатывает представление формы и имеет доступ к данным, которые пользователь ввел в форму. Стандартный способ добиться этого с помощью метода под названием «контролируемых компонентов» («controlled components»).

Контролируемые компоненты

В HTML, такие элементы формы как <input>, <textarea> и <select> содержат свое собственное состояние и обновляют его на основе пользовательского ввода. В React, изменяемое состояние, как правило, хранится в свойствах состояния компонентов, и обновляются только с setState().

Мы можем объединить оба подхода, делая React состояние «единственным источником истины». Затем React компонент, который визуализирует форму, контролирует, что происходит в этой форме на последующих стадиях ввода данных пользователем. Входной элемент формы, значение которого контролируется через React, называется «контролируемым компонентом».

Например, если мы хотим сделать предыдущий пример логировать имя, когда оно будет представлено, мы можем записать форму в качестве управляемого/контролируемого компонента:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Попробовать на CodePen.

Поскольку атрибут value  устанавливает в нашем элементе формы, отражаться значение будет всегда посредством this.state.value, React делает состояние источником изменений. С запуском handleChange на каждом нажатии клавиши обновляется состояние React, отображаемое значение будет обновляться по мере ввода данных пользователем.

С контролируемым компонентом, каждое изменяемое состояние будет иметь соответствующую функцию обработчик. Это упрощает  его изменение или проверку пользовательского ввода. Например, если мы хотим, чтобы обеспечить ввод имен заглавными буквами, мы могли бы написать handleChang как:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

Тег textarea

В HTML-элементе <textarea> текст является дочерним по отношению к элементу:

<textarea>
  Hello there, this is some text in a text area
</textarea>

В React, <textarea> вместо этого использует атрибут value . Таким образом, форма использующая <textarea> может быть записана в одну строку:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Обратите внимание на то, что this.state.value инициализируется в конструкторе.

Тег select

В HTML, <select> создаёт выпадающий список. Например, этот HTML список набор ароматов:

<select>
  <option value="grapefruit">Grapefruit Грейпфрут</option>
  <option value="lime">Lime Лайм</option>
  <option selected value="coconut">Coconut Кокос</option>
  <option value="mango">Mango Манго</option>
</select>

Обратите внимание, что опция Кокос изначально выбрана, из-за атрибута selected. React, вместо этого атрибута использует атрибут value корневого тега select. Это более удобно в контролируемом компоненте, потому что вам нужно только обновить его в одном месте. Например:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Попробовать на CodePen.

В целом, это делает работу <input type="text">, <textarea>, и <select> похожей — все они принимают значение атрибута, которое можно использовать для реализации контролируемого компонента.

Альтернативы контролируемых компонентов

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