function useRef(initialValue) {
if (!isInitialized[cursor]) {
memorizedStates[cursor] = { current: initialValue };
isInitialized[cursor] = true;
}
const memorizedState = memorizedStates[cursor];
cursor = cursor + 1;
return memorizedState;
}
closure를 사용해서 useRef를 사용하는 function componenet가 re render되더라도 주어진 값을 보존할 수 있도록 하였다.
const LoginForm = () => {
const [values, setValues] = React.useState({
email: "",
password: "",
});
const [errors, setErrors] = React.useState({
email: "",
password: "",
});
const [touched, setTouched] = React.useState({
email: false,
password: false,
});
const validate = (values) => {
const errors = {
email: "",
password: "",
};
if (!values.email) {
errors.email = "이메일을 입력하세요.";
}
if (!values.password) {
errors.password = "비밀번호를 입력하세요.";
}
return errors;
};
const handleChange = (e) => {
console.log(e.target.name);
console.log([e.target.name]);
setValues({
...values,
[e.target.name]: e.target.value,
});
};
const handleBlur = (e) => {
console.log("handleBlur", e.target.name);
console.log("handleBlur", [e.target.name]);
console.log("handleBlur ", touched);
console.log("handleBlur errors ", errors);
setTouched({
...touched,
[e.target.name]: true,
});
};
const handleSubmit = (e) => {
e.preventDefault();
const nextTouched = {
email: true,
password: true,
};
setTouched(nextTouched);
const errors = validate(values);
console.log(errors);
setErrors(errors);
if (Object.values(errors).some(Boolean)) return;
console.log("Submitted", values);
};
React.useEffect(() => {
console.log("UseEffect called");
setErrors(validate(values));
}, [values]);
return (
<form noValidate onSubmit={handleSubmit}>
<input
type="text"
name="email"
placeholder="Email"
autoFocus
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
></input>
{touched.email && errors.email && <span>{errors.email}</span>}
<input
type="password"
name="password"
placeholder="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
></input>
{touched.password && errors.password && <span>{errors.password}</span>}
<button>Login</button>
</form>
);
};
export default LoginForm;
브라우저에서 컨트롤하게 냅두지 않고 statae와 event handler를 통해서 react가 제어하는 component로 변경하여 다룬 예제이다.
blur, onchange, onsubmit 등 브라우저에서 발행되는 이벤트들을 react에서 handling함으로서 좀 더 섬세하게 다룰 수 있게 된다.
export const useForm = ({ initialValues, validate, onSubmit }) => {
const [values, setValues] = React.useState({ initialValues });
const [errors, setErrors] = React.useState({});
const [touched, setTouched] = React.useState({});
const handleChange = (e) => {
const nextValues = {
...values,
[e.target.name]: e.target.value,
};
setValues(nextValues);
};
const handleBlur = (e) => {
const nextTouched = {
...touched,
[e.target.name]: true,
};
setTouched(nextTouched);
};
const handleSubmit = (e) => {
e.preventDefault();
const nextTouched = Object.keys(values).reduce((touched, filed) => {
touched[filed] = true;
return touched;
}, {});
setTouched(nextTouched);
const errors = validate(values);
setErrors(errors);
if (Object.values(errors).some(Boolean)) return;
onSubmit(values);
};
React.useEffect(() => {
setErrors(validate(values));
}, [values]);
return {
values,
errors,
touched,
handleBlur,
handleSubmit,
handleChange,
};
};
custom hook으로 재사용할 수 있게 분리를 했다.
사용하는 쪽에서 초기화 state값이랑 validate 로직을 가지고 있는 함수랑 , onSubmit시 후속처리할 로직을 전달해주면 된다.
const LoginForm = () => {
const validate = (values) => {
const errors = {
email: "",
password: "",
};
if (!values.email) {
errors.email = "이메일을 입력하세요.";
}
if (!values.password) {
errors.password = "비밀번호를 입력하세요.";
}
return errors;
};
const { values, touched, errors, handleChange, handleBlur, handleSubmit } =
MyForm.useForm({
initialValues: { email: "", password: "" },
validate,
onSubmit: (values) => console.log("Submitted", values),
});
return (
<form noValidate onSubmit={handleSubmit}>
<input
type="text"
name="email"
placeholder="Email"
autoFocus
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
></input>
{touched.email && errors.email && <span>{errors.email}</span>}
<input
type="password"
name="password"
placeholder="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
></input>
{touched.password && errors.password && <span>{errors.password}</span>}
<button>Login</button>
</form>
);
};
export default LoginForm;
const getFieldProps = (name) => {
const value = values[name];
const onBlur = handleBlur;
const onChange = handleChange;
return {
name,
value,
onBlur,
onChange,
};
};
useForm custom hook에 자주 쓰이는 함수들을 props로 편하게 쓸 수 있게 전달 해준다.
const {
values,
touched,
errors,
handleSubmit,
getFieldProps,
} = MyForm.useForm({
initialValues: { email: "", password: "" },
validate,
onSubmit: (values) => console.log("Submitted", values),
});
요렇게 가져와서
<input
type="text"
name="email"
placeholder="Email"
autoFocus
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
></input>
<input
type="text"
placeholder="Email"
autoFocus
{...getFieldProps("email")}
></input>
좀 더 간편하게 줄여서 사용할 수 있다.
const formContext = React.createContext({});
formContext.displayName = "formContext";
export const Form = ({ children, ...rest }) => {
const formValue = useForm(rest);
return (
<formContext.Provider value={formValue}>
<form noValidate onSubmit={formValue.handleSubmit}>
{children}
</form>
</formContext.Provider>
);
};
export const Field = ({ as = "input", children, ...rest }) => {
const { getFieldProps } = React.useContext(formContext);
return React.createElement(
as,
{ ...rest, ...getFieldProps(rest.name) },
children
);
};
export const ErrorMessage = ({ name }) => {
const { touched, errors } = React.useContext(formContext);
if (!touched[name] || !errors[name]) return null;
return <span>{errors[name]}</span>;
};
React Context를 통해서 useForm custom hook을 provider에서 제공하고 이를 사용하는 2개의 Consumer 인 Field, ErrorMessage를 만들었다.
Field는 react element를 직접만드는데 기본 값으로 input element를 만들게 되고 넘겨받은 name으로 각각 알맞은
(email or password) props을 만들어서 생성한다.
const LoginForm = () => {
const validate = (values) => {
const errors = {
email: "",
password: "",
};
if (!values.email) {
errors.email = "이메일을 입력하세요.";
}
if (!values.password) {
errors.password = "비밀번호를 입력하세요.";
}
return errors;
};
const handleSubmit = (values) => console.log("Submitted", values);
return (
<MyForm.Form
initialValues={{ email: "", password: "" }}
validate={validate}
onSubmit={handleSubmit}
>
<MyForm.Field name="email"></MyForm.Field>
<MyForm.ErrorMessage name="email"></MyForm.ErrorMessage>
<MyForm.Field name="password"></MyForm.Field>
<MyForm.ErrorMessage name="password"></MyForm.ErrorMessage>
<button>Login</button>
</MyForm.Form>
);
};
export default LoginForm;
선언형 방식으로 굉장히 간결해진것을 확인할 수 있다.
'FrontEnd > [리액트 2부] 고급 주제와 훅' 카테고리의 다른 글
[4.4장 메모이제이션 훅] (0) | 2024.03.25 |
---|---|
[4.3 리듀서 훅] (0) | 2024.03.22 |
[3.5장 컨텍스트 훅] , custom hook (0) | 2024.03.16 |
[3.2장 상태 훅] (0) | 2024.03.12 |
[3.1장 클래스/함수 컴포넌트][3.2장 상태훅] (0) | 2024.03.11 |