Форми

У React HTML-елементи форм працюють дещо інакше, ніж інші DOM-елементи, тому що елементи форм від початку мають певний внутрішній стан. Наприклад, в цю HTML-форму можна ввести ім’я:

<form>
  <label>
    Ім'я:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Надіслати" />
</form>

За замовчуванням браузер переходить на іншу сторінку при відправленні HTML-форм, в тому числі і цієї. Якщо вас це влаштовує, то не треба нічого міняти, в React форми працюють як зазвичай. Однак найчастіше форму зручніше обробляти за допомогою JavaScript-функції, у якої є доступ до введених даних. Стандартний спосіб реалізації такої поведінки називається “керовані компоненти”.

Керовані компоненти

В 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('Ім\'я, що було надіслано: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Ім'я:
          <input type="text" value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Надіслати" />
      </form>
    );
  }
}

Спробуйте на CodePen

Оскільки ми встановили атрибут value для нашого елементу форми, відображене значення завжди буде this.state.value, що робить стан React “джерелом правди”. Оскільки handleChange працює при кожному натисканні клавіші для оновлення стану React, відображуване значення оновиться разом з вводом користувача.

У керованому компоненті значення поля введення завжди визначається станом React. Хоча це і означає, що ви повинні написати трохи більше коду, але тепер ви зможете передавати значення й іншим UI-елементам або скинути його з інших обробників подій.

Тег textarea

HTML-елемент <textarea> визначає текстові дані безпосередньо як текст, введений між відкриваючим та закриваючим тегами:

<textarea>
  Привіт, це текст у текстовій області
</textarea>

Натомість в React <textarea> використовує атрибут value. Таким чином, форма, яка використовує <textarea>, може бути написана дуже схоже до форми, яка використовує однорядковий ввід:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {      value: 'Будь ласка, напишіть твір про ваш улюблений елемент DOM.'    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('Твір, що було надіслано: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Надіслати" />
      </form>
    );
  }
}

Зверніть увагу на те, що this.state.value ініціалізується в конструкторі, так що область тексту з самого початку має певний текст.

Тег select

В HTML елемент <select> створює список, який можна відкрити. Наприклад, цей HTML створює список ароматів:

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

Зверніть увагу на те, що опція “Кокос” обрана за замовчуванням за допомогою атрибуту selected. Замість того, щоб використовувати атрибут selected, React використовує атрибут value кореневого тегу select. У керованому компоненті так зручніше, тому що потрібно оновити його лише в одному місці. Наприклад:

class FlavorForm 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('Ваш улюблений аромат: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Оберіть ваш улюблений аромат:
          <select value={this.state.value} onChange={this.handleChange}>            <option value="грейпфрут">Грейпфрут</option>
            <option value="лайм">Лайм</option>
            <option value="кокос">Кокос</option>
            <option value="манго">Манго</option>
          </select>
        </label>
        <input type="submit" value="Надіслати" />
      </form>
    );
  }
}

Спробуйте на CodePen

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

Примітка

Ви можете передати масив у атрибут value, що дозволяє вибрати декілька параметрів у тезі select:

<select multiple={true} value={['B', 'C']}>

Тег завантаження файлу

В HTML, <input type="file"> дозволяє користувачеві вибрати один або кілька файлів зі сховища його пристрою, які будуть завантажені на сервер або маніпулювати ними за допомогою JavaScript через File API.

<input type="file" />

Оскільки його значення доступне лише для читання, це некерований компонент у React. Це обговорюється разом з іншими некерованими компонентами пізніше в документації.

Обробка кількох полів введення даних

Коли вам потрібно обробляти декілька керованих елементів input, ви можете додати атрибут name до кожного елемента і дозволити функції-обробнику вибрати, що робити спираючись на значення event.target.name.

Наприклад:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value    });
  }

  render() {
    return (
      <form>
        <label>
          Триває:
          <input
            name="isGoing"            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Кількість гостей:
          <input
            name="numberOfGuests"            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

Спробуйте на CodePen

Зверніть увагу на те, як ми використали ES6-синтаксис для розрахованих імен властивостей, щоб оновити стан за ключами, які співпадають зі значенням атрибуту name відповідних полів введення:

this.setState({
  [name]: value});

Це те ж саме, що й такий ES5-код:

var partialState = {};
partialState[name] = value;this.setState(partialState);

Крім того, оскільки setState() автоматично об’єднує частковий стан у поточний стан, нам потрібно було лише викликати його зі зміненими частинами.

Значення null керованого поля введення даних

Встановлення значення пропсу value в керованому компоненті не дозволяє користувачеві змінювати введені дані, якщо ви цього не бажаєте. Якщо ви вказали value, але вхідні дані все ще можна редагувати, можливо, ви випадково встановили значення для value як undefined або null.

Наступний код демонструє це. (Поле вводу спочатку заблоковане, але стає доступним для редагування після невеликої затримки.)

ReactDOM.render(<input value="привіт" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

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

Іноді використання керованих компонентів може бути досить важким заняттям, оскільки вам потрібно написати обробник подій для кожного випадку, коли ваші дані можуть бути змінені і провести весь стан форми через компонент React. Це може стати особливо дратівливим, коли ви перетворюєте вже наявну кодову базу в React або інтегруєте програму React з бібліотекою, що не є реактивною. У таких ситуаціях можна спробувати некеровані компоненти як альтернативну техніку для обробки даних форм.

Повноцінні рішення

Якщо ви шукаєте комплексне рішення, включаючи перевірку, відстеження відвідуваних полів та обробку відправлення даних форми, то бібліотека Formik є одним з популярних варіантів. Однак вона теж побудована на тих же принципах, що і керовані компоненти та керування станом — тож не нехтуйте їх вивченням.