본문 바로가기
Language/React

[React 공식 Doc 가이드 #10] Forms

by ocwokocw 2021. 2. 11.

- 이 글은 React 공식 홈페이지 Docs v16.8.3 (https://reactjs.org/docs) 에 기반하여 작성한 글입니다.

- Forms

HTML form element 는 다른 DOM element 와 다르게 동작한다. 우리가 form submit 을 써봤으면 알 수 있듯이 내부의 상태(state) 를 가지면서 동작하기 때문이다.

HTML 에서 기본적으로 form 태그를 이용해서 submit 을 하면 다른 페이지로 이동하게 되어있다. 물론 React 에서도 원하면 똑같이 할 수 있다.

 

하지만 대부분의 경우에는 submit 을 다루는 javascript 함수를 정의하고 User 가 form 태그안에 들어왔을 때 data 에 접근하는 용도로 사용한다. 일반적으로 이런방식을 "controlled components" 라고 칭한다.


- Controlled Components

HTML 에서 <input>, <textarea>, <select> 같은 form element 들은 상태를 유지하거나 유저의 input 에 따라서 값을 갱신한다. React 에서는 state 로 상태 전이를 표현하고 setState() 함수를 통해서만 갱신한다.

 

React 의 state 를 단일 소스 저장소("SSOT - single source of truth") 로 만들어서 위의 두 가지 특성을 무리없이 소화할 수 있다. (form element 들이 값을 표시할 때 React의 state를 참조하고, user 가 입력을 할 때에도 React의 state 를 update 하여 data의 sync가 맞는다는 소리 인 것 같다.)

 

그리고 또 form 을 rendering 하는 React component 는 user 의 input 이후에 일어나는 일들을 제어한다. 이런 방법으로 React 에 의해 제어되는 input form element 를 "controlled component" 라고 부른다.

 

예제를 한번 보자. 만약 위에서 정의한 <form> 태그가 submit 될 때 name 을 로그로 남기고 싶다면 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" name="name" value={this.state.value} 
					onChange={this.handleChange}/>
			  </label>
			  <input type="submit" value="Submit" />
			</form>
		);
	}
}

ReactDOM.render(
	<NameForm />, 
	document.getElementById('root')
);

 

form element 에 value attribute 값이 세팅되었기 때문에 보여지는 값은 언제나 this.state.value 를 참조한다. (React state 가 소스 저장소가 되었다.)

 

handleChange 는 모든 키보드 입력마다 React의 state 를 갱신하여서 보여지는 값은 유저 입력에 따라서 갱신된다. 시간순으로 보면 아래와 같은 동작이 일어날 것이다.

 

유저입력 -> handleChange -> React 의 state 갱신 -> form element 가 React state를 참조

 

controlled component 에서 모든 state 의 변화는 handler 함수와 연관이 있는데 state 를 갱신하거나 user의 입력을 검증할 수도 있다.

 

예를 들어 우리가 입력을 강제로 대문자로 변경하길 원한다면 handleChange 함수를 변경할 수 있다.


- The textarea Tag

HTML 에서 <textarea> element 는 value 값을 세팅하지 않고 자식으로 text 를 참조하는 형태이다.

React 에서는 <textarea> 에 value attribute 를 이용할 수 있다. <textarea> 를 한줄로 사용하는 input form 들과 비슷한 문법으로 사용할 수 있게 해준다.

 

class EssayForm extends React.Component{

	constructor(props){
		super(props);
		
		this.state = {
			value: 'Please input text'
		};	
		
		this.handleChange = this.handleChange.bind(this);
		this.handleSubmit = this.handleSubmit.bind(this);
	}
	
	handleChange(event) {
		this.setState({value: event.target.value});
	}
	
	handleSubmit(event) {
		alert('essay is ' + this.state.value);
		event.preventDefault();
	}
	
	render() {
		return (
			<form onSubmit={this.handleSubmit}>
				<textarea value={this.state.value} 
					onChange={this.handleChange} />
				<input type="submit" value="Submit"/>
			</form>
			
		);
	}
}

ReactDOM.render(
	<EssayForm />, 
	document.getElementById('root')
);

생성자에서 value 값은 초기화했기 때문에 처음 app 이 구동되자마자 해당문자열이 보일 것이다.


- The select Tag

HTML 에서 <select> 태그는 drop-down 목록를 만든다. 문법예를 한번 살펴보자.

drop-down 목록중에 coconut 이 초기 선택값임을 알 수 있다.(selected attribute)

 

React 에서는 selected attribute 대신에 select 태그 레벨에 value attribute 를 사용해서 선택된 값을 표현한다. 이런 문법은controlled component 사용을 더 편리하게 해주는데 그 이유는 update 할 장소를 한곳으로 수렴시켜 주기 때문이다. select 태그를 이용한 예제를 한번 구성해보자.

 

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('selected value is ' + this.state.value);
		event.preventDefault();
	}
	
	render() {
		return (
			<form onSubmit={this.handleSubmit}>
				<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>
				<input type="submit" value="Submit"/>
			</form>
		);
	}
	
}

ReactDOM.render(
	<FlavorForm />, 
	document.getElementById('root')
);

React는 <input type="text>, <textarea>, <select> 와 같이 태그가 달라져도 value attribute 만을 이용해서 controlled component 를 구현할 수 있게 해준다.

 

그리고 <select> 태그 같은 경우 value attribute 에 multiple 값을 줄 경우 값을 가지고 있다. 아래 예제는 아무런 동작도 하지 않고 Submit 버튼을 곧바로 누른 경우인데 UI 를 표현하는 코딩은 하지 않았지만 alert 에 값들이 있는것을 확인할 수 있다.


- The file input Tag

HTML 에서 <input type="file"> 태그는 User 가 1개 이상의 파일을  서버에 업로드 하기 위해 특정 Device 에서 선택할 수 있게 도와준다.

 

file input 의 value 는 read-only 이기 때문에 React 에서는 uncontrolled component 로 취급한다.


- Handling Multiple Inputs

여러개의 controlled input element 가 필요할 때 각 element 에 name attribute 를 추가하면, 같은 handler function을 이용하더라도 event.target.name 값을 기반으로 제어할 수 있게 된다.

 

isGoing 과 numberOfGuests 라는 이름을 가진 input element 2개를 다루어 보자.

 

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>
		)
	}
}

ReactDOM.render(
	<Reservation />, 
	document.getElementById('root')
);

 

위에서 handleInputChange 함수 안에서 this.setState({[name]: value}) 로 변경하였는데 이는 ES6 computed property name 문법을 이용한 것이며, ES5로 동치문법은 아래와 같다.

알고 있듯이 setState() 함수가 자동으로 현재의 this.state 와 병합하기 때문에 값이 변하는 부분에서 단순히 setState를 호출하기만 하면 된다.


- Controlled Input Null Value

controlled component 에서 value attribute 에 값을 지정하면 user 가 input 값을 변경하는걸 막아준다. 만약 value 값을 특정지었지만 input 값이 수정가능한 상태가 되어야 한다면 value 을 undefined 나 null 로 설정하면 수정가능하게 할 수 있다.

 

아래코드를 보자.

처음에는 hi 에서 키보드로 입력을 하여도 값이 변하지 않지만 1초 후에는 수정가능한 상태가 된다.


- Alternatives to Controlled Components

때로는 controlled components 를 사용하는게 지루한 작업이 될 수도 있다. 모든 data 가 변할 수 있는 곳에 하나하나 event handler 를 정의해야하고, React Component 의 state 값도 동기화 시켜야 한다. 

 

만약 기존에 작성된 코드가 있고 이걸 React 로 변경시켜야 하거나, React 라이브러리가 아닌걸 React 와 병합해야 한다면 꽤나 성가신 작업이 되게 된다.

 

이런 상황에 처하게 되면 input form 들을 구현하기 위해서 대안으로 uncontrolled components 를 사용해 보길 원할 수도 있겠다. (uncontrolled components 참조 : https://reactjs.org/docs/uncontrolled-components.html)


- Fully-Fledged Solutions

공식 문서에서는 validation, form submit handling, field 에 대한 정보를 모두 추적해주는 솔루션을 찾고 있다면 Formik 를 사용해 보라고 추천하고 있다.

 

Formik link : https://jaredpalmer.com/formik/

 

하지만 controlled component 와 동일한 원리를 적용하고 있고 state 를 관리한다는 점은 똑같으므로 러닝커브 혹은 귀찮음에 대해 부정적으로 보지 말라는 당부도 하고 있다.

댓글