본 포스트는 Udemy 리액트 완벽가이드 2024 를 듣고 정리한 내용입니다.
목차 🌳
1️⃣ 폼을 다루는 어려움?
2️⃣ Form 제출을 다루는 방법 & 유효성 검사
3️⃣ Custom Hook으로 처리하기
Form 양식은 입력창의 집합이다! 근데 입력값 받아주기 뿐만 아니라, 유효성 검사도 해줘야 한다.
[Form 형식이 하는일]
1️⃣ Form 제출
-> 입력된 값은 state 관리 / 아니면 refs로 참조 / 것도 아니면 FormData 객체 사용
2️⃣ Input 유효성 검사
-> 키를 누를때마다 올바르지 않는 입력값에 대해 검증 결과 알려줌
-> 사용자 경험에 따라 언제 오류를 출력할지는 조금씩 다름
<form onSubmit={handleSubmit}>
<h2>Login</h2>
<div className="control-row">
<div className="control no-margin">
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" />
</div>
...
<p className="form-actions">
<button className="button button-flat">Reset</button>
<button className="button">Login</button>
</p>
</form>
Form 형태에서 제출을 누르게 된다면, Form 양식 요소에서 요청을 발생시키고 -> 그 요청이 서버로 전송되는게 일반적이다. 그치만 Form 제출 시, 브라우저는 기본적으로 페이지를 새로 고침하게 된다.
싱글 페이지 어플리케이션인 React에겐 전체 페이지를 로드하는 것은 날벼락 ! ⚡️⚡️
그렇기 때문에 필요한 부분만을 업데이트하는 방식을 고수하기 때문에, handleSubmit
안에
e.preventDefault()
을 수행해준다
아무튼 Form의 input을 받아내는 방법은 ~
1️⃣
State
관리를 통한 input value 관리
import { useState } from 'react';
export default function Login() {
const [enteredValue, setEnteredValue] = useState({
email: '',
passward: '',
});
function handleSubmit(e) {
e.preventDefault();
console.log(enteredValue);
}
function handleInputChange(identifier, e) {
setEnteredValue((prev) => ({
...prev,
[identifier]: e.target.value,
}));
}
return (
<form onSubmit={handleSubmit}>
<h2>Login</h2>
<div className="control-row">
<div className="control no-margin">
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
name="email"
onChange={(e) => handleInputChange('email', e)}
value={enteredValue.email}
/>
</div>
// ... 생략
</form>
);
}
2️⃣
Ref
v를 통한 input value 관리
import { useRef } from 'react';
export default function Login() {
const emailRef = useRef();
const passwordRef = useRef();
function handleSubmit(e) {
e.preventDefault();
const enteredEmail = emailRef.current.value;
const enteredPassword = passwordRef.current.value;
console.log(enteredEmail, enteredPassword);
}
return (
<form onSubmit={handleSubmit}>
<h2>Login</h2>
<div className="control-row">
<div className="control no-margin">
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" ref={emailRef} />
</div>
// ... 생략
);
}
두 방법 비교 🥊
사실 확실한 정답은 없지만, 따지고 봤을때 입력값의 갯수가 작고 간단할때는 Ref
가 더 간결
ref
가 간결한 사유) onChange
를 통해 입력에 대한 상태 변화를 제어할 필요가 없음
-> 그치만 단점, DOM
을 다룰때, 참조로 재설정 하는 등의 경우엔 좀 신중해야함
반대로 입력폼이 복잡할때) 그땐 useRef
로 연결할 값이 늘어나기 때문에 state
관리가 더 좋지 않을까
3️⃣
FormData
객체 활용하기
FormData
객체란,
HTML 폼 형태의 데이터를 쉽게 관리하고 전송할 수 있도록 해주는 API
key-value
를 쌍으로 저장 <input type="file">
를 통해 파일 전송도 쉽게 처리 가능FormData
사용 예시,
function handleSubmit(e) {
e.preventDefault();
const fd = new FormData(e.target); // form 요소를 전달
const data = Object.fromEntries(fd.entries()); // ⬇️ 하단에 설명 추가
const acquisitionChannel = fd.getAll('acquisition'); // name이 'acquisition'인 모든 값을 배열로 반환하여 저장
data.acquisition = acquisitionChannel; // acquisition 속성 추가 후, 그 값으로 acquisitionChannel 배열을 설정ㄴ
console.log(data);
}
entries()
는 fd 에 들어있는 모든 key-value 쌍을 포함하는 배열을 반환. [name, value]
Object.fromEntries()
는 위의 값을 사용해, key-value 쌍으로 구성된 객체로 반환 {name: value}
암튼 쉽게! 복잡한 형태의 Form을 다룰 수 있다!
1️⃣ form 요소에 reset 호출
e.target.reset();
2️⃣ state를 초기값으로 업데이트하기
setEnteredValue({ email: '', passward: ''});
유효성 검사는 그 시점에 따라 접근법이 달라지는데,
!제출시! 값들의 유효성을 검사할 경우에는 크게 방법에 구애받지 않지만,
만일 매번 !키 입력에 따라! 유효성 검사를 할 경우에는 => state를 이용해야 한다.
!키 입력에 따라! 유효성 검사를 할 경우
근데 타이핑을 치는 순간순간에 유효성을 검사할 경우, (ex. 이메일 형식 @ 들어가는지)
오류 문구가 일찍 보여지는 경향이 있다.
그렇기 때문에, onBlur 이벤트리스너를 통해서, 포커스를 잃은 경우 검증하되, 값을 입력하기 시작하면 오류 메시지가 사라지게 하는 것이 Best!!!!
function Login() {
const [enteredValue, setEnteredValue] = useState({
email: '',
passward: '',
});
// 유효성 검증할 시점
const [didEdit, setDifEdit] = useState({
email: false,
passward: false,
});
// 이메일 형식에 @가 있는지
const emailIsInvaild = didEdit.email && !enteredValue.email.includes('@');
// 입력시 (포커스 상태)일때, 검증 X
function handleInputChange(identifier, e) {
setEnteredValue((prev) => ({
...prev,
[identifier]: e.target.value,
}));
setDifEdit((prev) => ({
...prev,
[identifier]: false,
}));
}
// 포커스 아웃시, 검증 !
function handleEmailBlur(identifier) {
setDifEdit((prev) => ({
...prev,
[identifier]: true,
}));
}
// 이하 함수 생략..
return (
<form onSubmit={handleSubmit}>
<div className="control-row">
<div className="control no-margin">
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
name="email"
onChange={(e) => handleInputChange('email', e)}
onBlur={() => handleEmailBlur('email')}
value={enteredValue.email}
/>
<div className="control-error">{emailIsInvaild && <p>plz enter invalid email</p>}</div>
</div>
// ... 생략
</form>
);
}
!제출시! 값들의 유효성을 검사할 경우
1️⃣ 방법1 : 제출시 유효한지를 판별한 상태 하나 더 선언
const [emailIsInvalid, setEmailIsInvalid] = useState(false);
// .. 일부 생략
function handleSubmit(e) {
e.preventDefault();
const enteredEmail = emailRef.current.value;
console.log(enteredEmail);
const emailIsValid = enteredEmail.includes('@');
if (!emailIsValid) {
setEmailIsInvalid(true);
return;
}
setEmailIsInvalid(false);
}
2️⃣ 방법2 : 내장된 Prop을 사용
required
: input value가 공란인지 확인하고, input type에 따라 유효성 판별됨
<div className="control">
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" required />
</div>
minLength={num}
: 최소 입력글자수를 정하여, 유효하지 않을 경우 제출을 막음
<div className="control">
<label htmlFor="password">Password</label>
<input id="password" type="password" name="password" required minLength={8} />
</div>
1️⃣ 중복되는 label + input + error 코드는
Custom Component
로!
Input.jsx
export default function Input({ label, id, error, ...props }) {
return (
<div className="control no-margin">
<label htmlFor={id}>{label}</label>
<input id={id} {...props} />
<div className="control-error">{error && <p>{error}</p>}</div>
</div>
);
}
custom Component 사용
<Input
label="Email"
id="email"
type="email"
name="email"
onChange={handleEmailChange}
onBlur={handleEmailBlur}
value={emailValue}
error={emailHasError && 'Please enter a valid email!'}
/>
2️⃣ 유효성 검사 함수도 따로 모아두고 재사용하기!
📁 utils -> validations.js
export function isEmail(value) {
return value.includes('@');
}
export function isNotEmpty(value) {
return value.trim() !== '';
}
export function hasMinLength(value, minLength) {
return value.length >= minLength;
}
export function isEqualsToOtherValue(value, otherValue) {
return value === otherValue;
}
-> 이러면 boolean
으로 값이 반환!
3️⃣ input 에 대한 입력상태 관리 + 유효성 검증은
Custom Hook
으로!
📁 Hooks -> useInput.js
useInput.js
import { useState } from 'react';
export function useInput(defaultValue, validationFn) {
const [enteredValue, setEnteredValue] = useState(defaultValue);
const [didEdit, setDidEdit] = useState(false);
const valueIsValid = validationFn(enteredValue);
function handleInputChange(e) {
setEnteredValue(e.target.value);
setDidEdit(false);
}
function handleInputBlur() {
setDidEdit(true);
}
return { value: enteredValue, handleInputBlur, handleInputChange, hasError: didEdit && !valueIsValid };
}
custom hook 사용
const {
value: emailValue,
handleInputBlur: handleEmailBlur,
handleInputChange: handleEmailChange,
hasError: emailHasError,
} = useInput('', (value) => isEmail(value) && isNotEmpty(value));