react 로 form 을 만들어 사용하는 일은 생각보다 많은 걸 해야한다.
onSubmit
에 이벤트를 잡아서 event.target.fieldName.value
로 하나씩 체크를 해서 validating 을 하다가
- 귀찮아질 때 쯤 function 을 만들어서 관리를 하고
- 자주 사용하는 form input 을 컴포넌트로 만들며
- form 에 초기값을 넣어줘야할 경우 (edit 페이지 같은) 값을 가져와 하나하나 바인딩하고
- input 에 값이 바뀌었는지에 따라 submit 버튼을 활성, 비활성화 하는 로직도 있어야한다
번거로운 일을 하는 것 보다 redux-form 라이브러리를 사용해보자
검색시에 나오는 포스트들은 아주 기본적인 예제 밖에 없어서 문서를 매번 헤집는 시간이 필요했다.
먼저 이 라이브러리를 쓰기 위해선 다음 사전 지식이 필요하다
redux-form 의 리듀서를 연결시켜줘야한다
rootReducer
import { reducer as formReducer } from "redux-form";
const rootReducer = combineReducers({
form: formReducer,
});
export default rootRecuder;
redux-form component 와 redux-form 이 들어갈 compoent 를 만들어야한다
편의상 Login component와 LoginForm component라고 하자
Login
import React, { Component } from "react";
import { connect } from "react-redux";
import LoginForm from "LoginForm";
class Login extends Component {
submitLoginForm = (formData) => {
console.log("LoginFormData => ", formData);
};
render() {
return <LoginForm onSubmit={this.submitLoginForm} />;
}
}
export default connect()(Login);
LoginComponent에서는 딱히 특별한게 없이 LoginForm Component 를 호출하고 함수 하나를 내려준 게 끝이다
LoginForm
import React, { Component } from "react";
import { Field, reduxForm } from "redux-form";
class LoginForm extends Component {
render() {
const {
handleSubmit,
pristine,
submitting,
} = this.props;
return (
<form onSubmit={handleSubmit}>
<Field
name="id"
type="text"
component="input"
placeholder="아이디를 입력하세요"
/>
<Field
name="password"
type="password"
component="input"
placeholder="비밀번호를 입력하세요"
/>
</form>
);
}
}
export default reduxForm({
form: "LoginForm",
})(LoginForm);
redux-form에서 Field Component와 reduxForm High Order Component를 가져온 뒤
input tag 대신 Field Component 의 component 속성으로 input 을 만들어준다
그리고 해당 폼을 reduxForm 으로 감싸 redux 와 연결한다
handleSubmit
은 onSubmit prop 으로 들어온 함수를 폼과 연결해준다 (event.preventDefault()
및 폼의 필드들을 serialize 해서 object 로 바꿔 onSubmit 함수의 parameter 로 넘겨준다)
말이 좀 어렵다면 Login.js
의 submitLoginForm
속 console.log(formData)
의 구조를 보자
{
"id": "idValue",
"password": "passwordValue"
}
오 꽤 나이스하다
하지만 필드만 덩그라니 있는 경우는 없다 더 업그레이드 해보자
스타일이 필요한 필드
보통의 필드들은 label
이 들어가 있고, selectBox
, checkBox
의 경우엔 스타일을 주기위해 대부분 외부 라이브러리를 쓴다
이럴 때 Field Component 의 component 속성에 원하는 모양을 만들어서 넣어주면 된다
LoginForm
...
const renderInputField = ({ input, type, label, placeholder, meta: { touched, error }}) => {
return (
<div>
<label>{label}</label>
<input {...input} type={type} placeholder={placeholder} />
{ touched && error &&
<span className="error">{error}</span>
}
</div>
)
}
class LoginForm extends Component {
render() {
const {
handleSubmit,
pristine,
submitting
} = this.props
return (
<form onSubmit={handleSubmit}>
<Field
name="id"
type="text"
label="아이디"
component={renderInputField}
placeholder="아이디를 입력하세요"
/>
<Field
name="password"
type="password"
label="비밀번호"
component={renderInputField}
placeholder="비밀번호를 입력하세요"
/>
</form>
)
}
}
renderInputField
라는 함수형 컴포넌트를 만들어서 Field.component prop 에 넣긴했는데, div>input
을 렌더링 해주는 것만 보이고 엄청난 파라미터들 때문에 정신이 아득할 것이다
component prop 에 함수형 컴포넌트를 넣으면 field prop 을 받게 된다
여기엔 내려준 prop 뿐 아니라 reduxForm 에서 넣어준 prop 도 존재한다
원래 해당 렌더링 컴포넌트는 아래와 같은 모습이다
const renderInputField = (field) => {
return <div>...</div>;
};
그럼 먼저 reduxForm 에서 넣어준 prop 을 보자
- field.input: input 은 모든 HTML input attribute 를 가지고 있는게 아니라 다음 몇가지만 갖고 있다
- checked
- name
- onBlur
- onChange
- onDragStart
- onDrop
- onFocus
- value
- field.meta: meta 는 해당 input 의 상태 에 관한 데이터가 있다
- touched: 해당 input 이 한 번이라도 클릭이 되어졌는지
- valid: 해당 input 값이 valid 한지
- error: 해당 input 값에 에러가 있으면 error message 가 들어온다
- ... 나머진 문서를 참조하자
이제 type, label, placeholder 는 field.input prop 에 없어서 직접 넣어줬고, meta 는 이미 정의되어 있었구나 란 renderInputField 함수의 props 가 보일 것이고 Field Component 의 구조를 마음대로 변경할 수 있게 되었다
동적으로 value 를 바꿔야할 때
checkbox, select 의 경우엔 스타일이 들어가면 element 의 onClick 를 잡아서 hidden field 의 데이터를 바꿔줘야한다
checkbox 전체 영역을 클릭할 때마다 기존의 값을 toggle 해주는 component 를 만들 수 있다
LoginForm
const renderCheckboxField = ({ input, label }) => {
return (
<div onClick={(event) => input.onChange(!input.value)}>
<i
className={`fa ${!input.value ? "fa-square-o" : "fa-check-square-o"}`}
/>
<label>{label}</label>
<input type="hidden" {...input} />
</div>
);
};
value 에 따라서 font-awesome icon이 변경된다
함수로 값을 변경해야할 경우가 있을 수도 있다
이럴 땐 reduxForm prop 의 change
메소드를 사용하면 된다
바꿀 input 의 name 속성과 value 값을 넘겨주자
changeInputValue = (targetInputName, val) => {
this.props.change(targetInputName, val);
};
초기값
로그인 폼에는 아이디 저장 기능을 붙히면 폼이 초기화될 때 그 값을 가져와야한다
Login
...
class Login extends Component {
state = {
userId: ''
}
componentDidMount() {
const userId = localStorage.getItem('userId')
this.setState({
userId
})
}
render() {
return (
<LoginForm
onSubmit={this.submitLoginForm}
initialValues={{
id: this.state.userId
}}
/>
)
}
}
Login Component 에는 초기값을 가져와서 initialValues prop 안에 넣어준다
LoginForm
class LoginForm extends Component {
render() {
return <form>...</form>;
}
}
export default reduxForm({
form: "LoginForm",
enableReinitialize: true,
})(LoginForm);
localStorage.userId
에 값이 들어있다면 그 값으로 id Field 가 초기화 된다
컴포넌트가 로딩되기 전에 값이 정해져있다면 enableReinitialize
옵션이 없어도 되지만, 수정 폼처럼 기존 데이터 fetch 후 또는 componentDidMount 후에 값을 가져온다면 해당 옵션값을 true
로 넣어줘야한다
validation
여기까지 이해했다면 validation 은 큰 어려움 없이 문서의 예제소스를 참조하면 붙힐 수 있다
react 라이브러리들은 읽은 뒤 바로 적용하긴 힘들다고 생각한다 매번 삽질이 필요한듯