본문 바로가기
코딩 정보/React

[React] form 양식 다루기 (사용자 유효성 검사)

by 꽁이꽁설꽁돌 2024. 8. 2.
728x90
반응형

목차

     

    Form 제출 다루기

    아래와 같이 코드를 짜면 양식 요소에서 서버 요청을 하기 때문에 새로고침이 일어나 리액트에서는 올바르게 작동하지 않는다.

    export default function Login() {
    	 function handleSubmit() {
        console.log("submitted!");
      }
      return (
        <form>
          <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>
    
            <div className="control no-margin">
              <label htmlFor="password">Password</label>
              <input id="password" type="password" name="password" />
            </div>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button" onClick={handleSubmit}>Login</button>
          </p>
        </form>
      );
    }

     

    따라서 아래와 같이 코드를 짜서 새로고침을 막을 수 있다.

    export default function Login() {
      function handleSubmit(e) {
        e.preventDefault();
        console.log("submitted!");
      }
      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" />
            </div>
    
            <div className="control no-margin">
              <label htmlFor="password">Password</label>
              <input id="password" type="password" name="password" />
            </div>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">
              Login
            </button>
          </p>
        </form>
      );
    }

     

    상태를 이용한 사용자 값 변경

    아래와 같이 객체로 묶어 만들어 줌으로써 관리해야 하는 상태와 함수를 줄일 수 있다.
    식별자가 키포인트이다.

    import { useState } from "react";
    
    export default function StateLogin() {
      const [enteredValues, setEnteredValues] = useState({
        email: "",
        password: "",
      });
    
      function handleInputChange(identifier, event) {
        event.preventDefault();
        setEnteredValues((prevValues) => ({
          ...prevValues,
          [identifier]: event.target.value,
        }));
      }
      function handleSubmit(event){
        event.preventDefault();
        console.log(enteredValues);
      }
      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={(event) => handleInputChange("email", event)}
                value={enteredValues.email}
              />
            </div>
    
            <div className="control no-margin">
              <label htmlFor="password">Password</label>
              <input
                id="password"
                type="password"
                name="password"
                onChange={(event) => handleInputChange("password", event)}
                value={enteredValues.password}
              />
            </div>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">Login</button>
          </p>
        </form>
      );
    }

     

     

    Ref를 이용한 사용자 값 변경

    ref를 이용하면 코드를 줄일 수 있다는 장점이 있지만 입력 갯수가 늘어나면 일일히 모두 연결해 주어야 하기 때문에

    관리가 복잡해질 수 있다는 단점이 있다.

    import { useState } from "react";
    import { useRef } from "react";
    
    export default function RefLogin() {
      const password = useRef();
      const email = useRef();
      function handleSubmit(event) {
        event.preventDefault();
        console.log(password.current.value);
        console.log(email.current.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" ref={email} />
            </div>
    
            <div className="control no-margin">
              <label htmlFor="password">Password</label>
              <input id="password" type="password" name="password" ref={password} />
            </div>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">Login</button>
          </p>
        </form>
      );
    }

     

    FormData를 통해 더 복잡한 양식의 데이터 가져오기

    import { useState } from "react";
    
    export default function Signup() {
      const [passwordAreNotEqual, setPasswordAreNotEqual] = useState(false);
      function handleSubmit(event) {
        event.preventDefault();
        const fd = new FormData(event.target);
        
        //const enteredEmail = fd.get('email');
        
        const acquisitionChannel = fd.getAll("acquisition"); //따로 가져오기
        
        const data = Object.fromEntries(fd.entries()); //배열로 데이터 가져오기
        
        data.acquisition = acquisitionChannel; //데이터 객체를 합치기
    
       
       //제출했을 때 유효성 검사하기
        if (data.password !== data["confirm-password"]) {
          setPasswordAreNotEqual(true);
          return;
        }
        setPasswordAreNotEqual(false);
        event.target.reset();
        console.log(data);
      }
      return (
        <form onSubmit={handleSubmit}>
          <h2>Welcome on board!</h2>
          <p>We just need a little bit of data from you to get you started 🚀</p>
    
          <div className="control">
            <label htmlFor="email">Email</label>
            <input id="email" type="email" name="email" required />
          </div>
    
          <div className="control-row">
            <div className="control">
              <label htmlFor="password">Password</label>
              <input
                id="password"
                type="password"
                name="password"
                required
                minLength={6}
              />
            </div>
    
            <div className="control">
              <label htmlFor="confirm-password">Confirm Password</label>
              <input
                id="confirm-password"
                type="password"
                name="confirm-password"
                required
              />
              <div className="control-error">
                {passwordAreNotEqual && <p>Please must match.</p>}
              </div>
            </div>
          </div>
    
          <hr />
    
          <div className="control-row">
            <div className="control">
              <label htmlFor="first-name">First Name</label>
              <input type="text" id="first-name" name="first-name" required />
            </div>
    
            <div className="control">
              <label htmlFor="last-name">Last Name</label>
              <input type="text" id="last-name" name="last-name" required />
            </div>
          </div>
    
          <div className="control">
            <label htmlFor="phone">What best describes your role?</label>
            <select id="role" name="role">
              <option value="student">Student</option>
              <option value="teacher">Teacher</option>
              <option value="employee">Employee</option>
              <option value="founder">Founder</option>
              <option value="other">Other</option>
            </select>
          </div>
    
          <fieldset>
            <legend>How did you find us?</legend>
            <div className="control">
              <input
                type="checkbox"
                id="google"
                name="acquisition"
                value="google"
              />
              <label htmlFor="google">Google</label>
            </div>
    
            <div className="control">
              <input
                type="checkbox"
                id="friend"
                name="acquisition"
                value="friend"
              />
              <label htmlFor="friend">Referred by friend</label>
            </div>
    
            <div className="control">
              <input type="checkbox" id="other" name="acquisition" value="other" />
              <label htmlFor="other">Other</label>
            </div>
          </fieldset>
    
          <div className="control">
            <label htmlFor="terms-and-conditions">
              <input type="checkbox" id="terms-and-conditions" name="terms" />I
              agree to the terms and conditions
            </label>
          </div>
    
          <p className="form-actions">
            <button type="reset" className="button button-flat">
              Reset
            </button>
            <button type="submit" className="button">
              Sign up
            </button>
          </p>
        </form>
      );
    }

     

    매 상태 유효성 검사하기

    유효성 검사는 매 입력마다 확인해야 하기 떄문에 state를 활용한 코드에서 하고자 한다.

    또한 onBlur을 통해 더 상세한 유효성 검사를 구현했다.

    import { useState } from "react";
    
    export default function StateLogin() {
      const [enteredValues, setEnteredValues] = useState({
        email: "",
        password: "",
      });
    
    //포커싱을 잃으면 보이게끔 하기 위한 state
      const [didEdit, setDidEdit] = useState({
        email: false,
        password: false,
      });
    
      const emailIsInvalid = didEdit.email && !enteredValues.email.includes("@");
    
      function handleInputChange(identifier, event) {
        event.preventDefault();
        setEnteredValues((prevValues) => ({
          ...prevValues,
          [identifier]: event.target.value,
        }));
        //다시 입력을 시작하면 false로 바꾸어 준다.
        setDidEdit((prevEdit) => ({
          ...prevEdit,
          [identifier]: false,
        }));
      }
      function handleSubmit(event) {
        event.preventDefault();
        console.log(enteredValues);
      }
    
      function handleInputBlur(identifier) {
        setDidEdit((prevEdit) => ({
          ...prevEdit,
          [identifier]: true,
        }));
      }
      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"
                
                //포커스를 잃을 때 보여줌
                onBlur={() => handleInputBlur("email")}
                onChange={(event) => handleInputChange("email", event)}
                value={enteredValues.email}
              />
              <div className="control-error">
                {emailIsInvalid && <p>Please enter a valid email address</p>}
              </div>
            </div>
    
            <div className="control no-margin">
              <label htmlFor="password">Password</label>
              <input
                id="password"
                type="password"
                name="password"
                onBlur={() => handleInputBlur("password")}
                onChange={(event) => handleInputChange("password", event)}
                value={enteredValues.password}
              />
            </div>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">Login</button>
          </p>
        </form>
      );
    }

     

    제출 기반의 유효성 검사하기

    제출 시에만 유효성을 검사하는 코드이다. 위 아래 모두 장단점이 있어 선택의 영역이지만

    제출 기반의 유효성 검사는 항상 있는 편이 좋다.

    import { useState } from "react";
    import { useRef } from "react";
    
    export default function RefLogin() {
      const [emailInvalid, setEmailInvalid] = useState(false);
      const password = useRef();
      const email = useRef();
      function handleSubmit(event) {
        event.preventDefault();
        const enteredEmail = email.current.value;
        const emailIsInvalid = enteredEmail.includes('@');
        if (!emailIsInvalid) {
          setEmailInvalid(true);
          return;
        }
        setEmailInvalid(false);
        console.log("sending HTTP request...");
      }
      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={email} />
              <div className="control-error">
                {emailInvalid && <p>Please enter a valid email address</p>}
              </div>
            </div>
    
            <div className="control no-margin">
              <label htmlFor="password">Password</label>
              <input id="password" type="password" name="password" ref={password} />
            </div>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">Login</button>
          </p>
        </form>
      );
    }

     

    유효성 검사를 위한 로직 분리하기 (컴포넌트화)

     

    유효성 검사 조건 함수 파일

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

     

    상태를 기반으로 한 파일의 input 컴포넌트화

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

     

    상태를 기반으로 한 로그인

    import { useState } from "react";
    import Input from "./Input";
    import { isEmail, isNotEmpty, hasMinLength } from "../util/validation";
    export default function StateLogin() {
      const [enteredValues, setEnteredValues] = useState({
        email: "",
        password: "",
      });
    
      const [didEdit, setDidEdit] = useState({
        email: false,
        password: false,
      });
    
      const emailIsInvalid =
        didEdit.email &&
        !isEmail(enteredValues.email) &&
        !isNotEmpty(enteredValues.email);
      const passwordIsInvalid =
        didEdit.password && !hasMinLength(enteredValues.password, 6);
      function handleInputChange(identifier, event) {
        event.preventDefault();
        setEnteredValues((prevValues) => ({
          ...prevValues,
          [identifier]: event.target.value,
        }));
        setDidEdit((prevEdit) => ({
          ...prevEdit,
          [identifier]: false,
        }));
      }
      function handleSubmit(event) {
        event.preventDefault();
        console.log(enteredValues);
      }
    
      function handleInputBlur(identifier) {
        setDidEdit((prevEdit) => ({
          ...prevEdit,
          [identifier]: true,
        }));
      }
      return (
        <form onSubmit={handleSubmit}>
          <h2>Login</h2>
    
          <div className="control-row">
            <Input
              id="email"
              type="email"
              name="email"
              //포커스를 잃을 때 보여줌
              onBlur={() => handleInputBlur("email")}
              onChange={(event) => handleInputChange("email", event)}
              value={enteredValues.email}
              error={emailIsInvalid && "Please enter a valid email!"}
            ></Input>
            <Input
              id="password"
              type="password"
              name="password"
              onBlur={() => handleInputBlur("password")}
              onChange={(event) => handleInputChange("password", event)}
              value={enteredValues.password}
              error={passwordIsInvalid && "Please enter a valid password!"}
            ></Input>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">Login</button>
          </p>
        </form>
      );
    }

     

    Custom Hook을 활용하여 외부로 독립적인 상태 만들기

    useInput.jsx

    import { useState } from "react";
    
    export default function useInput(defaultValue, validationFn) {
      const [enteredValue, setEnteredValue] = useState(defaultValue);
      const [didEdit, setDidEdit] = useState(false);
    
      const valueIsValid = validationFn(enteredValue);
    
      function handleInputChange(event) {
        event.preventDefault();
        setEnteredValue(event.target.value);
        setDidEdit(false);
      }
      function handleInputBlur() {
        setDidEdit(true);
      }
      return {
        value: enteredValue,
        handleInputBlur,
        handleInputChange,
        hasError: didEdit && !valueIsValid,
      };
    }

     

    StateLogin.jsx

    import { useState } from "react";
    import Input from "./Input";
    import { isEmail, isNotEmpty, hasMinLength } from "../util/validation";
    import useInput from "../hooks/useInput";
    export default function StateLogin() {
      const {
        value: emailValue,
        handleInputChange: handleEmailChange,
        handleInputBlur: handleEmailBlur,
        hasError: emailIsInvalid,
      } = useInput("", (value) => {
        return isEmail(value) && isNotEmpty(value);
      });
    
      const {
        value: passwordValue,
        handleInputChange: handlePasswordChange,
        handleInputBlur: handlePasswordBlur,
        hasError: passwordIsInvalid,
      } = useInput("", (value) => {
        return hasMinLength(value, 6);
      });
    
      function handleSubmit(event) {
        event.preventDefault();
        if (emailIsInvalid || passwordIsInvalid) return;
        console.log(emailValue, passwordValue);
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <h2>Login</h2>
    
          <div className="control-row">
            <Input
              id="email"
              type="email"
              name="email"
              //포커스를 잃을 때 보여줌
              onBlur={handleEmailBlur}
              onChange={handleEmailChange}
              value={emailValue}
              error={emailIsInvalid && "Please enter a valid email!"}
            ></Input>
            <Input
              id="password"
              type="password"
              name="password"
              onBlur={handlePasswordBlur}
              onChange={handlePasswordChange}
              value={passwordValue}
              error={passwordIsInvalid && "Please enter a valid password!"}
            ></Input>
          </div>
    
          <p className="form-actions">
            <button className="button button-flat">Reset</button>
            <button className="button">Login</button>
          </p>
        </form>
      );
    }

     

    반응형