Сейчас мы начнем разбираться с более сложными элементами формы: с textarea, с чекбоксами, селектами и радио кнопочками.

Работа с textarea

Работа с текстареа осуществляется очень похожим на работу с инпутом образом - также добавляется атрибут value и событие onChange.

Запустите следующий код - и вы увидите на экране textarea, в который можно будет повводить текст. Все изменения будут отображаться в абзаце над нашим textarea:

class App extends React.Component {
	constructor() {
		super();
		this.state = {value: 'привет'};
	}

	//Записываем value текстареа в this.state.value:
	handleChange(event) {
		this.setState({value: event.target.value});
	}

	render() {
		return <div>
			<p>текст: {this.state.value}</p>
			<textarea value={this.state.value} onChange={this.handleChange.bind(this)} />
		</div>;
	}
}

Обратите внимание: React подход отличается подхода на чистом JavaScript. На чистом JS у текстареа нет атрибута value, кроме того, в чистом JS текстареа должен иметь закрывающий тег: <textarea></textarea>, но в реакте мы пишем так: <textarea /> - подобно работе с инпутом.

Чекбоксы

Работа с чекбоксами также осуществляется по схожему принципу, только вместо атрибута value мы указываем атрибут checked.

Если в этот атрибут передать true - то чекбокс будет отмечен, а если false - не будет отмечен:

return <div>
	<input
		type="checkbox"
		checked={тут должно быть true или false}
	/>
</div>;

Конечно же, обычно в checked передается стейт, который может принимать только два значения: или true, или false. Пусть для примера это будет стейт this.state.checked:

return <div>
	<input
		type="checkbox"
		checked={this.state.checked}
	/>
</div>;

Так же, как и при работе с инпутами, если жестко задать значение атрибута checked - состояние чекбокса нельзя будет сменить.

Для решения этой проблемы поступим так: будем по изменению чекбокса менять this.state.checked на противоположное ему значение, то есть на !this.state.checked.

Давайте сделаем это. Добавим в наш чекбокс атрибут onChange, к которому привяжем метод handleCheckboxChange:

return <div>
	<input
		type="checkbox"
		checked={this.state.checked}
		onChange={this.handleCheckboxChange.bind(this)}
	/>
</div>;

Напишем реализацию метода handleCheckboxChange:

handleCheckboxChange(event) {
	this.setState({checked: !this.state.checked});
}

Давайте соберем все вместе и запустим:

class App extends React.Component {
	constructor() {
		super();
		this.state = {checked: true};
	}

	//Меняем this.state.checked на противоположный:
	handleCheckboxChange(event) {
		this.setState({checked: !this.state.checked});
	}

	render() {
		return <div>
			<input
				type="checkbox"
				checked={this.state.checked}
				onChange={this.handleCheckboxChange.bind(this)}
			/>
		</div>;
	}
}

Запустите этот код и понажимайте на чекбокс - теперь при нажатии состояние чебокса будет меняться.

Давайте теперь сделаем так, чтобы состояние чекбокса выводилось на экран. Напрямую, вот так - {this.state.checked} - это сделать нельзя, так как там содержится логическое значение.

Воспользуемся тернарным оператором и сделаем так, чтобы если чекбокс отмечен - на экран выводилась строка 'отмечен', а в противном случае - 'не отмечен':

<p>Состояние: {this.state.checked ? 'отмечен' : 'не отмечен'}</p>

Добавим изменения в наш код:

class App extends React.Component {
	constructor() {
		super();
		this.state = {checked: true};
	}

	//Меняем this.state.checked на противоположный:
	handleCheckboxChange(event) {
		this.setState({checked: !this.state.checked});
	}

	render() {
		return <div>
			<p>Состояние: {this.state.checked ? 'отмечен' : 'не отмечен'}</p>
			<input
				type="checkbox"
				checked={this.state.checked}
				onChange={this.handleCheckboxChange.bind(this)}
			/>
		</div>;
	}
}

Запустите этот код и понажимайте на чекбокс - вы увидите, как в абзаце выводится текущее состояние чекбокса.

Селекты

Давайте теперь займемся выпадающими списками select. Работа с ними также практически не отличается от работы с инпутами и чекбоксами.

Пусть мы хотим сделать вот такой селект:

<select>
	<option>html</option>
	<option>css</option>
	<option>javascript</option>
	<option>php</option>
</select>

Давайте сделаем его. Добавим ему в value стейт this.state.value, а к событию onChange привяжем метод handleSelectChange. В общем, делаем все, как обычно:

render() {
	return <div>
		<select
				value={this.state.value}
				onChange={this.handleSelectChange.bind(this)}
		>

				<option>html</option>
				<option>css</option>
				<option>javascript</option>
				<option>php</option>

		</select>
	</div>;
}

Напишем реализацию метода handleSelectChange:

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

В this.state.value положим значение 'javascript' - в этом случае по умолчанию будет выбран именно этот пункт списка:

constructor() {
	super();
	this.state = {value: 'javascript'};
}

Сделаем также абзац, в который будет выводиться выбранный пункт списка:

<p>Ваш выбор: {this.state.value}</p>

Давайте соберем все вместе и запустим. Изначально выбран будет пункт списка со значением 'javascript' и этот выбор будет отражаться в нашем абзаце. Но если выбор поменять - в абзаце он тоже поменяется:

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

	//Изменяем this.state.value при изменении селекта:
	handleSelectChange(event) {
		this.setState({value: event.target.value});
	}

	render() {
		return <div>
			<p>Ваш выбор: {this.state.value}</p>
			<select
				value={this.state.value}
				onChange={this.handleSelectChange.bind(this)}
			>

				<option>html</option>
				<option>css</option>
				<option>javascript</option>
				<option>php</option>

			</select>
		</div>;
	}
}

Запустите этот код и проверьте, что все работает, как и описано.

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

Давайте теперь тегам option дадим атрибут value, как это обычно и делается:

<select>
	<option value="0">Язык HTML</option>
	<option value="1">Язык CSS</option>
	<option value="2">Язык JavaScript</option>
	<option value="3">Язык PHP</option>
</select>

На самом деле без атрибута value, как это было сделано выше - плохо. Почему плохо: ведь текст option может меняться и это повлияет на работу нашего скрипта. К примеру, вместо текста 'javascript' мы сделаем текст 'Язык JavaScript' и теперь строчка this.state = {value: 'javascript'} просто не будет работать.

А вот если мы привяжемся к value и напишем this.state = {value: 2} - все будет работать независимо от того, что написано в самом тексте тега option.

Давайте добавим атрибуты value нашим тегам option:

class App extends React.Component {
	constructor() {
		super();
		this.state = {value: 0};
	}

	//Изменяем this.state.value при изменении селекта:
	handleSelectChange(event) {
		this.setState({value: event.target.value});
	}

	render() {
		return <div>
			<p>Ваш выбор: {this.state.value}</p>
			<select
				value={this.state.value}
				onChange={this.handleSelectChange.bind(this)}
			>

				<option value="0">Язык HTML</option>
				<option value="1">Язык CSS</option>
				<option value="2">Язык JavaScript</option>
				<option value="3">Язык PHP</option>

			</select>
		</div>;
	}
}

Запустите этот код и повыбирайте разные значения в селекте. Вы увидите проблему - в абзац будет выводится не значение тега option, а значение атрибута value!

Итак, если вы запустите этот код, то увидите проблему - строка <p>Ваш выбор: {this.state.value}</p> будет выводить value нашего option, а никак не значение.

Для того, чтобы поправить эту проблему (она пока не исчезнет, об этом ниже), вначале сделаем следующее: не будем создавать теги option вручную, а сделаем массив this.state.langs и из этого массива и сформируем наш селект, вот так:

class App extends React.Component {
	constructor() {
		super();
		this.state = {
			value: 'html',
			langs: [
				'Язык HTML',
				'Язык CSS',
				'Язык JavaScript',
				'Язык PHP',
			]
		};
	}

	//Изменяем this.state.value при изменении селекта:
	handleSelectChange(event) {
		this.setState({value: event.target.value});
	}

	render() {
		
		//Формируем в цикле набор из тегов <option>:
		const options = this.state.langs.map((item, index) => {
			return <option key={index} value={index}>{item}</option>;
		});

		return <div>
			<p>Ваш выбор: {this.state.value}</p>
			<select
				value={this.state.value}
				onChange={this.handleSelectChange.bind(this)}
			>

				{options}

			</select>
		</div>;
	}
}

Результатом этого кода будет следующее:

<select>
	<option value="0">Язык HTML</option>
	<option value="1">Язык CSS</option>
	<option value="2">Язык JavaScript</option>
	<option value="3">Язык PHP</option>
</select>

Сейчас у нас по-прежнему есть строка this.state.value, которая выводит value оптиона option, а не его значение:

<p>Ваш выбор: {this.state.value}</p>

Давайте поправим ее так, чтобы она подтягивала значение из массива this.state.langs по его ключу this.state.value:

<p>Ваш выбор: {this.state.langs[this.state.value]}</p>

Добавим эту строчку в наш код и теперь все будет ок:

class App extends React.Component {
	constructor() {
		super();
		this.state = {
			value: 0, //тут теперь задаем значение из атрибута value
			langs: [
				'Язык HTML',
				'Язык CSS',
				'Язык JavaScript',
				'Язык PHP',
			]
		};
	}

	//Изменяем this.state.value при изменении селекта:
	handleSelectChange(event) {
		this.setState({value: event.target.value});
	}

	render() {
		
		//Формируем в цикле набор из тегов <option>:
		const options = this.state.langs.map((item, index) => {
			return <option key={index} value={index}>{item}</option>;
		});

		return <div>
			<p>Ваш выбор: {this.state.langs[this.state.value]}</p>
			<select
				value={this.state.value}
				onChange={this.handleSelectChange.bind(this)}
			>

				{options}

			</select>
		</div>;
	}
}

Запустите этот код и повыбирайте значения в селекте - теперь в абзаце действительно будут появляется выбранные значения.

Радиокнопочки

Работа с радиокнопочками radio несколько отличается, к примеру, от тех же чекбоксов.

Проблема в том, что у нескольких радио будет один и тот же стейт, но разные value.

Поэтому работа происходит следующем образом: в атрибут value записывают значение радио, а в атрибут checked - специальное условие, которое проверяет, равен ли this.state.option определенному значению. Если равен - радиокнопочка станет отмеченной, а если не равен - будет не отмеченной.

Пример: пусть this.state.option - это стейт, в котором будет хранится значение радио. Сделаем 2 радиокнопочки и дадим одной value="option1", а второй - value="option2".

Тогда для проверки того, будет ли первая радиокнопочка отмечена, ей в атрибут checked запишем следующее: checked={this.state.option == 'option1'}.

Получится, что если в this.state.option лежит option1 первая радиокнопочка будет отмеченной, а если не лежит - то не будет.

Аналогичную строку запишем и для второй радиокнопочки - checked={this.state.option == 'option2'}, только здесь мы сравниванием this.state.option уже с option2.

Для каждой радио также напишем атрибут onChange и прикрепим к нему метод handleRadioChange. Ну, здесь все как обычно - так же, как для инпутов и чекбоксов.

Давайте соберем все вместе и запустим:

class App extends React.Component {
	constructor() {
		super();
		this.state = {option: 'option1'};
	}

	//Изменяет this.state.option при изменении радио:
	handleRadioChange(event) {
		this.setState({option: event.target.value});
	}

	render() {
		return <div>

			<p>Ваш выбор: {this.state.option}</p>

			<input
				name="lang"
				type="radio"
				value="option1"
				checked={this.state.option == 'option1'}
				onChange={this.handleRadioChange.bind(this)}
			/>

			<input
				name="lang"
				type="radio"
				value="option2"
				checked={this.state.option == 'option2'}
				onChange={this.handleRadioChange.bind(this)}
			/>

		</div>;
	}
}

Запустите этот код и понажимайте на радиокнопочки - вы будете видеть ваш выбор в соответствующем абзаце.

Значение по умолчанию

Иногда бывает следующая ситуация: вы хотите сделать так, чтобы по умолчанию в инпуте уже было какое-то значение.

При этом вы хотите, чтобы из стейта было взято только начальное значение инпута, а сам инпут не был бы привязан к этому стейту.

Способ сделать это есть: нужно воспользоваться атрибутом defaultValue:

class App extends React.Component {
	constructor() {
		super();
		this.state = {value: 'привет'};
	}

	render() {
		return <div>
			<input defaultValue={this.state.value} />
		</div>;
	}
}

Запустите этот код - и вы увидите на экране инпут со значением 'привет'. Но это значение можно будет сменить, при этом this.state.value не поменяется - двухсторонней связи нет.

Для чекбоксов существует аналогичный атрибут defaultChecked, с помощью которого можно задать начальное состояние (отмечен или нет):

class App extends React.Component {
	constructor() {
		super();
		this.state = {checked: true};
	}

	render() {
		return <div>
			<input
				type="checkbox"
				defaultChecked={this.state.checked}
			/>
		</div>;
	}
}

Запустите этот код - и вы увидите на экране отмеченный чекбокс. Если понажимать на этот чекбокс, то его состояние будет меняться, а this.state.checked при этом меняться не будет - двухсторонней связи нет.