Работа с формой
ОБНОВЛЕНИЕ 2018: в учебнике хорошая теория, но ему уже два года. Проверяйте версии пакетов. За выходом нового учебника можно следить в telegram канале или twitter
На канале так же проводятся бесплатные вебинары, публикуются переводы и авторские материалы, присоединяйтесь!
Работа с формой
В данном уроке мы превратим наш input в форму добавления новости. Научимся работать с чекбоксами, disabled атрибутом кнопки и прочими стандартными для такой задачи вещами.
Результатом добавления новости, пока что, вновь, будет alert с текстом новости.
Переименуйте <TestInput />
в <Add />
, и рендерите в нем следующую форму: автор (input), текст новости (textarea), "я согласен с правилами" (checkbox), "показать alert" (button).
Попутно изменим названия классов, удалим лишние обработчики и переместим компонент <Add />
перед заголовком "Новости".
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 />
:
.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, и добавьте функцию обработчик.
...
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 кнопке.
<button
className='add__btn'
onClick={this.onBtnClickHandler}
ref='alert_button'
disabled>
Показать alert
</button>
Решение 2: с использованием state
добавьте getInitialState компоненту
<Add />
;удалите defaultChecked из инпута;
сделайте атрибут disabled у кнопки равным значению из state;
измените функцию обработчик;
Приведу полный код компонента <Add />
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>
);
}
});

Какое решение выбрать?
В данном случае я за второй вариант. Для меня большим злом является необходимость работать с DOM, чем "рендер" компонента <Add />
на каждый клик по чекбоксу. В данный момент я могу гарантировать, что никакой проблемы с производительностью не будет. Но заметьте, для input'a я все же оставил по прежнему схему работы через DOM. Путано? Я руководствовался тем, что клик по чекбоксу - событие не частое, а возможно единичное. <input />
и <textarea></textarea>
- более вредные в моем понимании. События onChange происходят в них слишком часто.
Работайте как вам нравится, просто знайте, что есть два варианта.
Для добавления новости нам осталось сформировать текст, который будет показываться в alert. Я думаю, эта задача вам точно под силу. Решение ниже.
Решение:
Достаточно всего лишь подкорректировать функцию onBtnClickHandler
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()
Подсказка #2: вам потребуется больше переменных, так же лучше переименовать переменную btnIsDisabled...
...
getInitialState: function() { //устанавливаем начальное состояние (state)
return {
agreeNotChecked: true,
authorIsEmpty: true,
textIsEmpty: true
};
},
...
Подсказка #3: объявите обработчики на onChange
...
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 - кнопка выключается.
<button
className='add__btn'
onClick={this.onBtnClickHandler}
ref='alert_button'
disabled={agreeNotChecked || authorIsEmpty || textIsEmpty}
>
Показать alert
</button>
Решение: код компонента <Add />
полностью:
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 согласно нашей логике.
Как передать аргумент в функцию? Что насчет такого варианта:
<input
type='text'
className='add__author'
onChange={this.onFieldChange('имя_переменной')}
placeholder='Ваше имя'
ref='author'
/>
Откровенно плохой вариант. Функция сразу же выполнится - так как указаны ().
Нам нужно передать именно функцию, а не результат ее выполнения. На помощь приходит метод bind().
Верной строкой для onChange в таком случае будет:
Для input'a:
onChange={this.onFieldChange.bind(this, 'authorIsEmpty')}
Для textarea:
onChange={this.onFieldChange.bind(this, 'textIsEmpty')}
Напишем саму функцию onFieldChange:
...
onFieldChange: function(fieldName, e) {
if (e.target.value.trim().length > 0) {
this.setState({[''+fieldName]:false})
} else {
this.setState({[''+fieldName]:true})
}
},
...
Я надеюсь, не возникает вопросов, откуда взялся аргумент fieldName, почему аргумент 'e' теперь второй? (если что, это все bind виноват, он "прокинул" нашу переменную в функцию первым аргументом, можно было прокинуть еще...)
Так как в переменной у нас строка, мы не можем передать ее напрямую в setState в качестве названия поля объекта.
Возможно вам покажется более читаемым такой вариант:
...
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.
По традиции - исходный код на данный момент.
Last updated
Was this helpful?