В сегодняшней части перевода учебного курса по React вам предлагается выполнить практическое задание по работе с формами.
> Часть 1: обзор курса, причины популярности React, ReactDOM и JSX
> Часть 2: функциональные компоненты
> Часть 3: файлы компонентов, структура проектов
> Часть 4: родительские и дочерние компоненты
> Часть 5: начало работы над TODO-приложением, основы стилизации
> Часть 6: о некоторых особенностях курса, JSX и JavaScript
> Часть 7: встроенные стили
> Часть 8: продолжение работы над TODO-приложением, знакомство со свойствами компонентов
> Часть 9: свойства компонентов
> Часть 10: практикум по работе со свойствами компонентов и стилизации
> Часть 11: динамическое формирование разметки и метод массивов map
> Часть 12: практикум, третий этап работы над TODO-приложением
> Часть 13: компоненты, основанные на классах
> Часть 14: практикум по компонентам, основанным на классах, состояние компонентов
> Часть 15: практикумы по работе с состоянием компонентов
> Часть 16: четвёртый этап работы над TODO-приложением, обработка событий
> Часть 17: пятый этап работы над TODO-приложением, модификация состояния компонентов
> Часть 18: шестой этап работы над TODO-приложением
> Часть 19: методы жизненного цикла компонентов
> Часть 20: первое занятие по условному рендерингу
> Часть 21: второе занятие и практикум по условному рендерингу
> Часть 22: седьмой этап работы над TODO-приложением, загрузка данных из внешних источников
> Часть 23: первое занятие по работе с формами
> Часть 24: второе занятие по работе с формами
> Часть 25: практикум по работе с формами
App
, который находится в файле App.js
стандартного проекта, создаваемого средствами create-react-app. Вот этот код:import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {}
}
render() {
return (
<main>
<form>
<input placeholder="First Name" /><br />
<input placeholder="Last Name" /><br />
<input placeholder="Age" /><br />
{/* Здесь создайте переключатели для выбора пола */}
<br />
{/* Здесь создайте поле со списком для выбора пункта назначения */}
<br />
{/* Здесь создайте флажки для указания диетологических ограничений */}
<br />
<button>Submit</button>
</form>
<hr />
<h2><font color="#3AC1EF">Entered information:</font></h2>
<p>Your name: {/* Имя и фамилия */}</p>
<p>Your age: {/* Возраст */}</p>
<p>Your gender: {/* Пол */}</p>
<p>Your destination: {/* Пункт назначения */}</p>
<p>
Your dietary restrictions:
{/* Список диетологических ограничений */}
</p>
</main>
)
}
}
export default App
this.state = {
firstName: "",
lastName: "",
age: 0,
gender: "",
destination: "",
dietaryRestrictions: []
}
age
, в котором предполагается хранить возраст, введённый пользователем. Возможно, иначе нужно будет поступить и с системой хранения данных флажков, которая сейчас представлена свойством dietaryRestrictions
, инициализированным пустым массивом.name
таким образом, чтобы они совпадали с именами свойств состояния, в которых будут храниться данные, введённые в эти поля. У них должен быть атрибут value
, значение которого определяется на основе данных, хранящихся в состоянии. При вводе данных в каждое из этих полей нужно передавать введённые данные компоненту, что приводит к необходимости наличия у них обработчика события onChange
. Все эти рассуждения приводят к тому, что описание полей выглядит теперь так:<input
name="firstName"
value={this.state.firstName}
onChange={this.handleChange}
placeholder="First Name"
/>
<br />
<input
name="lastName"
value={this.state.lastName}
onChange={this.handleChange}
placeholder="Last Name"
/>
<br />
<input
name="age"
value={this.state.age}
onChange={this.handleChange}
placeholder="Age"
/>
onChange
этих полей, указан несуществующий пока this.handleChange
. Создадим этот метод:handleChange(event) {
const {name, value} = event.target
this.setState({
[name]: value
})
}
event.target
свойства name
и value
, после чего используем их для установки соответствующего свойства состояния. В данный момент нас такой код универсального обработчика событий устроит, но позже, когда мы доберёмся до работы с флажками, мы внесём в него изменения.this
, выполняемой в конструкторе компонента:this.handleChange = this.handleChange.bind(this)
firstName
, secondName
и age
, поработаем с соответствующими элементами <p>
, приведя их к следующему виду:<p>Your name: {this.state.firstName} {this.state.lastName}</p>
<p>Your age: {this.state.age}</p>
age
, то есть — 0. Нам же нужно, чтобы в незаполненном поле выводилась бы подсказка. Попробуем заменить значение age
в состоянии на null
. После этого оказывается, что форма выглядит так, как нужно, но в консоли выводится следующее предупреждение, касающееся поля age
:Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components
age
на пустую строку, приведя код инициализации состояния к следующему виду:this.state = {
firstName: "",
lastName: "",
age: "",
gender: "",
destination: "",
dietaryRestrictions: []
}
age
вернётся подсказка. При заполнении полей введённые данные будут выводиться в нижней части страницы.<label>
, что позволит не только подписать переключатель, но и сделать так, чтобы щелчок по этой подписи, то есть — по его родительскому элементу, приводил бы к его выбору.checked
, и текстовых полей, у которых есть атрибут value
. Переключатели формируют группу, в которой каждому из переключателей назначают одно и то же имя, при этом свойство переключателей checked
устанавливается по условию, настроенному так, чтобы нельзя было бы включить больше одного переключателя, входящего в одну и ту же группу. В качестве обработчика событий onChange
переключателей назначим this.handleChange
.<label>
<input
type="radio"
name="gender"
value="male"
checked={this.state.gender === "male"}
onChange={this.handleChange}
/> Male
</label>
<br />
<label>
<input
type="radio"
name="gender"
value="female"
checked={this.state.gender === "female"}
onChange={this.handleChange}
/> Female
</label>
<p>
, расположенный в нижней части страницы, следующим образом:<p>Your gender: {this.state.gender}</p>
checked
, выдать true
. После щелчка по одному из них в состояние попадает соответствующее ему значение (хранящееся в атрибуте value
переключателя), переключатель оказывается выбранным, соответствующий текст выводится в нижней части формы.<select>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
<select>
и у находящихся в нём тегов <option>
есть атрибут value
. Однако эти атрибуты несут различный смысл. То значение value
, которое назначают элементу <option>
, указывает на то, каким должно быть соответствующее свойство состояния при выборе данного элемента. Сюда попадают те строки, которые должны находиться в выпадающем списке. В нашем случае — это некие пункты назначения, например — страны. Запишем их названия с маленькой буквы для того, чтобы их внешний вид соответствовал бы значениям свойств value
других имеющихся в коде элементов. После этого код поля со списком будет выглядеть так:<select value=>
<option value="germany">Germany</option>
<option value="norway">Norway</option>
<option value="north pole">North Pole</option>
<option value="south pole">South Pole</option>
</select>
value
тега <select>
, то тут будет указано не некое жёстко заданное значение, а ссылка на соответствующее свойство состояния:<select value={this.state.destination}>
<option value="germany">Germany</option>
<option value="norway">Norway</option>
<option value="north pole">North Pole</option>
<option value="south pole">South Pole</option>
</select>
onChange
— this.handleChange
.<select
value={this.state.destination}
name="destination"
onChange={this.handleChange}
>
<option value="germany">Germany</option>
<option value="norway">Norway</option>
<option value="north pole">North Pole</option>
<option value="south pole">South Pole</option>
</select>
<p>
, в который будет выводиться то, что выбрано в поле destination
:<p>Your destination: {this.state.destination}</p>
Your destination:
после двоеточия ничего не выводится.germany
, нужно, открыв поле со списком, сначала выбрать что-нибудь другое, а потом уже — пункт Germany
.-- Please Choose a destination --
. В нашем случае это может выглядеть так:<select
value={this.state.destination}
name="destination"
onChange={this.handleChange}
>
<option value="">-- Please Choose a destination --</option>
<option value="germany">Germany</option>
<option value="norway">Norway</option>
<option value="north pole">North Pole</option>
<option value="south pole">South Pole</option>
</select>
dietaryRestrictions
, которое планируется использовать для работы с флажками, было инициализировано пустым массивом. Теперь, когда дело дошло до работы с элементами управления, возникает такое ощущение, что лучше будет представить это поле в виде объекта. Так будет удобнее работать с сущностями, представляющими отдельные флажки в виде свойств этого объекта с понятными именами, а не в виде элементов массива. Свойства объекта, который теперь будет представлен свойством состояния dietaryRestrictions
, будут содержать логические значения, указывающие на то, сброшен ли соответствующий флажок (false
) или установлен (true
). Теперь код инициализации состояния будет выглядеть так:this.state = {
firstName: "",
lastName: "",
age: "",
gender: "",
destination: "",
dietaryRestrictions: {
isVegan: false,
isKosher: false,
isLactoseFree: false
}
}
<label>
и настроив их атрибуты. Вот как будет выглядеть их код:<label>
<input
type="checkbox"
name="isVegan"
onChange={this.handleChange}
checked={this.state.dietaryRestrictions.isVegan}
/> Vegan?
</label>
<br />
<label>
<input
type="checkbox"
name="isKosher"
onChange={this.handleChange}
checked={this.state.dietaryRestrictions.isKosher}
/> Kosher?
</label>
<br />
<label>
<input
type="checkbox"
name="isLactoseFree"
onChange={this.handleChange}
checked={this.state.dietaryRestrictions.isLactoseFree}
/> Lactose Free?
</label>
dietaryRestrictions
, а в качестве значений их атрибутов checked
— конструкции вида this.state.dietaryRestrictions.isSomething
.onChange
указан уже имеющийся у нас обработчик this.handleChange
, мы должны, для обеспечения правильной работы программы, внести в него некоторые изменения.event.target
, в дополнение к уже извлечённым, свойства type
и checked
. Первое нужно для проверки типа элемента (флажки имеют тип, представленный строкой checkbox
), второе — для того чтобы выяснить, установлен флажок или снят. Если оказывается, что обработчик был вызван после взаимодействия пользователя с флажком, используем особую процедуру установки состояния. События других элементов управления будем обрабатывать так же, как и прежде. handleChange
к следующему виду. Тут мы исходим из предположения о том, что свойства объекта dietaryRestrictions
вполне можно менять по одному:handleChange(event) {
const {name, value, type, checked} = event.target
type === "checkbox" ?
this.setState({
dietaryRestrictions: {
[name]: checked
}
})
:
this.setState({
[name]: value
})
}
First Name
, всё тоже будет работать как и прежде, но при попытке установки одного из флажков будет выдано следующее предупреждение:Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: fb.me/react-controlled-components
dietaryRestrictions
, можно воспользоваться функциональной формой setState
, самостоятельно сформировав новую версию состояния. Если бы нам надо было бы управлять большим количеством флажков, то, вероятно, мы так бы и сделали. Но тут мы поступим иначе. А именно, сделаем свойства объекта dietaryRestrictions
свойствами состояния, избавившись от этого объекта:this.state = {
firstName: "",
lastName: "",
age: "",
gender: "",
destination: "",
isVegan: false,
isKosher: false,
isLactoseFree: false
}
dietaryRestrictions
:<label>
<input
type="checkbox"
name="isVegan"
onChange={this.handleChange}
checked={this.state.isVegan}
/> Vegan?
</label>
<br />
<label>
<input
type="checkbox"
name="isKosher"
onChange={this.handleChange}
checked={this.state.isKosher}
/> Kosher?
</label>
<br />
<label>
<input
type="checkbox"
name="isLactoseFree"
onChange={this.handleChange}
checked={this.state.isLactoseFree}
/> Lactose Free?
</label>
<p>Your dietary restrictions:</p>
<p>Vegan: {this.state.isVegan ? "Yes" : "No"}</p>
<p>Kosher: {this.state.isKosher ? "Yes" : "No"}</p>
<p>Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}</p>
App
:import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
firstName: "",
lastName: "",
age: "",
gender: "",
destination: "",
isVegan: false,
isKosher: false,
isLactoseFree: false
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const {name, value, type, checked} = event.target
type === "checkbox" ?
this.setState({
[name]: checked
})
:
this.setState({
[name]: value
})
}
render() {
return (
<main>
<form>
<input
name="firstName"
value={this.state.firstName}
onChange={this.handleChange}
placeholder="First Name"
/>
<br />
<input
name="lastName"
value={this.state.lastName}
onChange={this.handleChange}
placeholder="Last Name"
/>
<br />
<input
name="age"
value={this.state.age}
onChange={this.handleChange}
placeholder="Age"
/>
<br />
<label>
<input
type="radio"
name="gender"
value="male"
checked={this.state.gender === "male"}
onChange={this.handleChange}
/> Male
</label>
<br />
<label>
<input
type="radio"
name="gender"
value="female"
checked={this.state.gender === "female"}
onChange={this.handleChange}
/> Female
</label>
<br />
<select
value={this.state.destination}
name="destination"
onChange={this.handleChange}
>
<option value="">-- Please Choose a destination --</option>
<option value="germany">Germany</option>
<option value="norway">Norway</option>
<option value="north pole">North Pole</option>
<option value="south pole">South Pole</option>
</select>
<br />
<label>
<input
type="checkbox"
name="isVegan"
onChange={this.handleChange}
checked={this.state.isVegan}
/> Vegan?
</label>
<br />
<label>
<input
type="checkbox"
name="isKosher"
onChange={this.handleChange}
checked={this.state.isKosher}
/> Kosher?
</label>
<br />
<label>
<input
type="checkbox"
name="isLactoseFree"
onChange={this.handleChange}
checked={this.state.isLactoseFree}
/> Lactose Free?
</label>
<br />
<button>Submit</button>
</form>
<hr />
<h2><font color="#3AC1EF">Entered information:</font></h2>
<p>Your name: {this.state.firstName} {this.state.lastName}</p>
<p>Your age: {this.state.age}</p>
<p>Your gender: {this.state.gender}</p>
<p>Your destination: {this.state.destination}</p>
<p>Your dietary restrictions:</p>
<p>Vegan: {this.state.isVegan ? "Yes" : "No"}</p>
<p>Kosher: {this.state.isKosher ? "Yes" : "No"}</p>
<p>Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}</p>
</main>
)
}
}
export default App
К сожалению, не доступен сервер mySQL