HTML form element는 form element 자체가 내부 state를 가지기 때문에, React의 다른 DOM element와 다르게 동작한다.
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
위 예시에서, 사용자가 form을 제출하면 새로운 페이지로 이동하는 default HTML form 동작을 수행한다.
React에서 동일한 동작을 원한다면 위의 form을 그대로 사용하면 된다. 그러나 대부분의 경우, JavaScript 함수로 form을 처리하고 사용자가 form에 입력한 데이터에 접근하도록 하는 등의 임의의 동작을 원한다.
이를 위한 표준 방식은 controlled component라고 불리는 기술을 이용하는 것이다.
HTML에서는 <input>, <textarea>, <select>와 같은 form element는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트한다.
React에서는 변경할 수 있는 state가 일반적으로 component의 state 속성에 유지되며, setState() 메서드에 의해서만 업데이트된다.
우리는 React state로 위 두 방식의 특징을 결합할 수 있다. 그리고 form을 렌더링하는 React component는 form에 사용자가 입력하면서 발생할 수 있는 일을 제어할 수 있다.
이러한 방식으로 React에 의해 값이 제어되는 입력 form element를 controlled component라고 한다.
예를 들어, submit할 때 이름을 기록하길 원한다면 form을 controlled component로 작성할 수 있다.
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>
);
}
}
value attribute는 form element에 설정되므로 표시되는 값은 항상 this.state.value가 된다.
handleChange는 모든 keystroke에서 React state를 업데이트하기 때문에, 표시되는 값은 사용자의 타이핑에따라 업데이트 된다.
controlled component를 사용하면, 입력되는 값은 항상 React state를 변경한다. 이말은 즉, 코드를 조금만 더 작성하면 입력한 값을 다른 UI element에게도 넘겨줄 수도 있고, 다른 이벤트 핸들러로 초기화할 수도 있다는 뜻이다.
HTML에서 <textarea> element는 텍스트를 자식으로 정의한다.
<textarea>
Hello there, this is some text in a text area
</textarea>
React에서 <textarea>는 value attribute 사용한다. <textarea>를 사용하는 form은 single-line input을 사용하는 form과 비슷하게 작성할 수 있다.
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>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
HTML에서 <select> element는 드롭 다운 list를 만든다.
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
HTML에서는 selected attribute로 초기값을 명시하지만, React에서는 selected attribute를 사용하는 대신 최상단 select tag에 value attribute를 사용해서 초기값을 명시한다.
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 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>
);
}
}
<input type="text">, <textarea>, <select> 모두 비슷하게 동작한다. 모두 controlled component를 구현하는데 value attribute를 사용한다.
여러 input element를 제어해야할 때, 각 element에 name attribute를 추가하고, 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>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}