양식 및 사용자 입력 작업

맛없는콩두유·2022년 8월 30일
0
post-thumbnail

이번에는 사용자 입력 폼에 대해 폼이 어떻게 복잡한지 다루고,
리액트에서 폼과 입력 값을 조작하고 검증하는 것에 대해 다루겠습니다.

이를 개발자로서 작업할 수 있게 해주는 몇가지 도구들과 접근방법에 대해서도 알아보겠습니다!

양식 제출 처리 및 사용자 입력 값 가져오기

  • SimpleInput.js
  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를 적용하였습니다.

원하는 결과를 얻을 수 있습니다.

"was touched" State 처리하기

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가 호출 됐을 떄 추가해주면 완성입니다!

리펙토링 및 State 파생

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

을 추가하면 완성이다!

사용자 지정 입력 훅 추가하기

현재 코드에서 중복되는 부분이 너무 많습니다.

  • hooks 폴더를 만들어 use-input.js 추가하여 커스텀 훅을 만들어보겠습니다.
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을 해줍니다.

  • SimpleInput.js
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;

한층 더 간결해진 코드를 볼 수 있습니다.

profile
하루하루 기록하기!

0개의 댓글