이번에는 사용자 입력 폼에 대해 폼이 어떻게 복잡한지 다루고,
리액트에서 폼과 입력 값을 조작하고 검증하는 것에 대해 다루겠습니다.
이를 개발자로서 작업할 수 있게 해주는 몇가지 도구들과 접근방법에 대해서도 알아보겠습니다!
return (
<form onSubmit={formSubmissionHandler}>
<div className="form-control">
<label htmlFor="name">Your Name</label>
<input
ref={nameInputRef}
type="text"
id="name"
onChange={nameInputChangeHandler}
value={enteredName}
/>
</div>
<div className="form-actions">
<button>Submit</button>
</div>
</form>
);
사용자 입력 폼에서 input의 입력값을 가져오기 위해서는 2가지 방법이 있습니다.
바로 ref/useState를 사용하는 방법인데 두 가지 방법에 대해서 알아보겠습니다.
import { useRef, useState } from "react";
먼저, useState로 하는 방법에 대해서 알아보겠습니다. 맨 위에 import를 해줍니다.
const [enteredName, setEnteredName] = useState("");
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
const formSubmissionHandler = (event) => {
event.preventDefault();
console.log(enteredName);
setEnteredName("");
};
};
위 구문을 적어주면 form 속성에서 제출이 되었을 떄 event 값을 console로 불러올 수 있고 주의할 점은 event.preventDefault 메서드를 이용해 기본으로 제출됐을 떄 페이지가 이동되는 속성을 막아주는 것이 포인트이고, input 속성 값에 value로 {enteredName}을 적어주어야 setEnteredName(""); 공백이 적용되는 것이 포인트입니다.
두번쨰로 useRef로 하는 방법에 대해 알아보겠습니다.
const nameInputRef = useRef();
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
const formSubmissionHandler = (event) => {
event.preventDefault();
const enteredValue = nameInputRef.current.value;
console.log(enteredValue);
nameInputRef.current.value = ''; =>
};
ref는 current 속성을 이용해서 value 값을 가져오고, input에 속성으로 ref={nameInputRef} 속성을 적어주는 것이 포인트입니다.
키 입력마다 상태를 입력하고 입력된 값을 초기화해야하기 때문에 여기서는 useState가 어울린다!
입력창에 Test입력 시 콘솔창에서 확인이 가능한 것을 볼 수 있다.
현재 입력 칸에 빈 값을 제출하면 빈 값이 콘솔 창에 뜨고있습니다. 이 빈 값의 데이터는 불필요한 데이터입니다. 이것을 기본적인 유효성 검사를 통해 막아보도록 하겠습니다.
const formSubmissionHandler = (event) => {
event.preventDefault();
if (enteredName.trim() === "") {
return;
}
console.log(enteredName);
const enteredValue = nameInputRef.current.value;
console.log(enteredValue);
간단합니다. 제출될 때 if문을 추가하여 if문 안의 내용이 return되게하여 그 뒤에 부분이 실행안되게 해주면 됩니다. trim() 메서드는 공백도 포함하여 빈 문자인지 검증해주는 메서드입니다.
공백을 입력하였을 떄 사용자에게 알려주도록 만들어보겠습니다.
const [enteredNameIsValid, setEnteredNameIsValid] = useState(true);
먼저, 새로운 state를 생성해줍니다.
{!enteredNameIsValid && (
<p className="error-text">Name must not be empty.</p>
)}
enteredNameIsValid가 false일 때만 인용구가 나오게 적용을 하고,
const nameInputClasses = enteredNameIsValid
? "from-contrl"
: "form-contrl invalid";
<div className={nameInputClasses}>
입력창의 색깔도 enteredNameIsValid가 true일 때와 false일 때의 다른 css속성을 주도록 동적 css를 적용하였습니다.
원하는 결과를 얻을 수 있습니다.
const [enteredNameIsValid, setEnteredNameIsValid] = useState(true);
유효성 검사를 할 떄 처음 기본 값이 true로 주어서 처음 화면에 빨간색 글씨와 빨간 화면이 안보이게 트릭을 주었습니다. 이것은 잘못된 것입니다.
useEffect(() => {
if (enteredNameIsValid) {
console.log("Name Input is valid! ");
}
}, [enteredNameIsValid]);
만약 useEffect 구문이 있다고 생각하면 useState기본 값이 true이기 때문에 콘솔창에 Name input is valid!가 처음부터 뜨는 것을 확인할 수 있습니다.
const [enteredNameIsValid, setEnteredNameIsValid] = useState(false);
따라서 false로 바꿔주는 것이 맞는 것이고 false로 할 경우 처음부터 빨강색 글씨가 뜨는 것이 확인 되는데,
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
State를 추가하여 이를 해결할 수 있습니다!
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
유효하지않고 터치가 되었을 때 nameInputIsInvalid로 설정하고
const nameInputClasses = nameInputIsInvalid
? "from-contrl invalid"
: "form-contrl ";
{nameInputIsInvalid && (
<p className="error-text">Name must not be empty.</p>
)}
전에 했던 부분에 수정해서 nameInputIsInvalid를 넣어주어야 하고, 이 떄 지난 시간과 반대로 속성값을 넣어주어야 합니다.
그리고 마지막으로 제출하였을 떄 사용자가 모든 입력을 터치했다고 생각하여 setEnteredNameTouched(true);로 바꿔주면 완성됩니다!
const formSubmissionHandler = (event) => {
event.preventDefault();
setEnteredNameTouched(true);
UI 관점에서 사용자가 입력칸을 클릭하고 나서 바깥 화면을 누르게 됐을 떄도, 유효성 검사를 통해 빨간 화면이 보이게 해보겠습니다!
<input
ref={nameInputRef}
type="text"
id="name"
onChange={nameInputChangeHandler}
onBlur={nameInputBlurHandler}
value={enteredName}
/>
onBlur를추가하고
const nameInputBlurHandler = (event) => {
setEnteredNameTouched(true);
if (enteredName.trim() === "") {
setEnteredNameIsValid(false);
return;
}
};
nameInputBlurHandler가 호출 됐을 떄 추가해주면 완성입니다!
import { useState } from "react";
const SimpleInput = (props) => {
const [enteredName, setEnteredName] = useState("");
const [enteredNameTouched, setEnteredNameTouched] = useState(false);
const enteredNameIsValid = enteredName.trim() !== "";
const nameInputIsInvalid = !enteredNameIsValid && enteredNameTouched;
const nameInputChangeHandler = (event) => {
setEnteredName(event.target.value);
};
const nameInputBlurHandler = (event) => {
setEnteredNameTouched(true);
};
const formSubmissionHandler = (event) => {
event.preventDefault();
setEnteredNameTouched(true);
if (!enteredNameIsValid) {
return;
}
console.log(enteredName);
// nameInputRef.current.value = ''; => NOT IDEAL, DON'T MANIPULATE THE DOM
setEnteredName("");
setEnteredNameTouched(false);
};
const nameInputClasses = nameInputIsInvalid
? "form-control invalid"
: "form-control";
return (
<form onSubmit={formSubmissionHandler}>
<div className={nameInputClasses}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
onChange={nameInputChangeHandler}
onBlur={nameInputBlurHandler}
value={enteredName}
/>
{nameInputIsInvalid && (
<p className="error-text">Name must not be empty.</p>
)}
</div>
<div className="form-actions">
<button>Submit</button>
</div>
</form>
);
};
export default SimpleInput;
키 입력마다 유효성 검사 진행하겠고
trim() !== 으로 바꾸고 true로 바꾸고 return 문 없애고
event.taget.value.trim()으로 바꿔야 최신상태로 바로 유지가능
반복되는 것이 많다. ref가 필요 없기떄문에 지운다.
useEffect 필요없고
const enteredNameISValid enteredName.trim() 빈 문자가 아니라면을 넣어서 리펙토링을 해준다.
전체 폼에 대하여 유효성 검사를 진행해보겠습니다!
useEffect를 이용하여 진행할 수도 있지만 간결한 코드를 위해서 사용하지 않고,
let formIsValid = false;
if (enteredNameIsValid) {
formIsValid = true;
}
<button disabled={!formIsValid}>Submit</button>
button:disabled,
button:disabled:hover,
button:disabled:active {
background-color: #ccc;
color: #292929;
border-color: #ccc;
cursor: not-allowed;
}
css코드도 추가해줍니다!
입력했을 때만 버튼이 활성화되는 것을 볼 수 있습니다!
Name에 유효성 검사를 했듯이 이메일에도 똑같이 추가해보겠습니다! 이번에는 간단하게 @이 포함되어있는지 include 메서드를 통하여 확인해보겠습니다!
const [enteredEmail, setEnteredEmail] = useState("");
const [enteredEmailTouched, setEnteredEmailTouched] = useState(false);
const enteredEmailIsValid = enteredEmail.includes("@");
const enteredEmailIsInvalid = !enteredEmailIsValid && enteredEmailTouched;
emailState와 emailTouched에 대한 State를 만들어주고 includes를 이용하여 @가 포함되어있는지 확인한 후 포함안되었을떄와 터치가되었을 떄를 enteredEmailISInvalid에 담아둔다.
const emailInputChangeHandler = (event) => {
setEnteredEmail(event.target.value);
};
const emailInputBlurHandler = (event) => {
setEnteredEmailTouched(true);
};
을 추가하고 form이 제출되었을 떄 formSubmissionHandler 안에
setEnteredEmail("");
setEnteredEmailTouched(false);
const emailInputClasses = enteredEmailIsInvalid
? "form-control invalid"
: "form-control";
를 추가한다.
<div className={emailInputClasses}>
<label htmlFor="email">Your E-Mail</label>
<input
type="email"
id="email"
onChange={emailInputChangeHandler}
onBlur={emailInputBlurHandler}
value={enteredEmail}
/>
{enteredEmailIsInvalid && (
<p className="error-text">Please enter a valid email.</p>
)}
</div>
마지막으로 속성들을 바꿔주고
let formIsValid = false;
if (enteredNameIsValid && enteredEmailIsValid) {
formIsValid = true;
}
을 추가하면 완성이다!
현재 코드에서 중복되는 부분이 너무 많습니다.
const [enteredValue, setEnteredValue] = useState("");
const [isTouched, setIsTouched] = useState(false);
먼저, use-input.js에 SimpleInput.js에서 가져와서 우리는 이름과 이메일에 대해 다뤄야하기 떄문에 각각 enteredValue/setEnteredValue isTouched/setIsTouched 으로 이름을 바꿔줍니다.
const valueIsValid = validateValue(enteredValue);
const hasError = !valueIsValid && isTouched;
마찬가지로 유효성 검사 부분도 이름을 바꿔줍니다.
const valueChangeHandler = (event) => {
setEnteredValue(event.target.value);
};
const inputBlurHandler = (event) => {
setIsTouched(true);
};
const reset = () => {
setEnteredValue("");
setIsTouched(false);
};
다음으로 valueChange와 Blur, reset되었을 때 함수를 만들어주고
return {
value: enteredValue,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
reset,
};
이 것을 사용할 수 있게 return을 해줍니다.
import useInput from "../hooks/use-input";
const {
value: enteredName,
isValid: enteredNameIsValid,
hasError: nameInputHasError,
valueChangeHandler: nameChangedHandler,
inputBlurHandler: nameBlurHandler,
reset: resetNameInput,
} = useInput((value) => value.trim() !== "");
여기까지 name에 대해 커스텀 훅을 사용하였습니다.
다음으로 email에 대해 커스텀 훅을 적용해보겠습니다.
const {
value: enteredEmail,
isValid: enteredEmailIsValid,
hasError: emailInputHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: resetEmailInput,
} = useInput((value) => value.includes("@"));
를 추가해주고 안쓰는 부분을 지워줍니다.
import useInput from "../hooks/use-input";
const SimpleInput = (props) => {
const {
value: enteredName,
isValid: enteredNameIsValid,
hasError: nameInputHasError,
valueChangeHandler: nameChangedHandler,
inputBlurHandler: nameBlurHandler,
reset: resetNameInput,
} = useInput((value) => value.trim() !== "");
const {
value: enteredEmail,
isValid: enteredEmailIsValid,
hasError: emailInputHasError,
valueChangeHandler: emailChangeHandler,
inputBlurHandler: emailBlurHandler,
reset: resetEmailInput,
} = useInput((value) => value.includes("@"));
let formIsValid = false;
if (enteredNameIsValid && enteredEmailIsValid) {
formIsValid = true;
}
const formSubmissionHandler = (event) => {
event.preventDefault();
if (!enteredNameIsValid) {
return;
}
console.log(enteredName);
// nameInputRef.current.value = ''; => NOT IDEAL, DON'T MANIPULATE THE DOM
resetNameInput();
resetEmailInput();
};
const nameInputClasses = nameInputHasError
? "form-control invalid"
: "form-control";
const emailInputClasses = emailInputHasError
? "form-control invalid"
: "form-control";
return (
<form onSubmit={formSubmissionHandler}>
<div className={nameInputClasses}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
onChange={nameChangedHandler}
onBlur={nameBlurHandler}
value={enteredName}
/>
{nameInputHasError && (
<p className="error-text">Name must not be empty.</p>
)}
</div>
<div className={emailInputClasses}>
<label htmlFor="email">Your E-Mail</label>
<input
type="email"
id="email"
onChange={emailChangeHandler}
onBlur={emailBlurHandler}
value={enteredEmail}
/>
{emailInputHasError && (
<p className="error-text">Please enter a valid email.</p>
)}
</div>
<div className="form-actions">
<button disabled={!formIsValid}>Submit</button>
</div>
</form>
);
};
export default SimpleInput;
한층 더 간결해진 코드를 볼 수 있습니다.