본문 바로가기

React

[React] react-hook-form(Material UI 로그인 페이지 구현)

React Hook Form 이란?

React에서 Form을 쉽게 만들기 위한 라이브러리로 성능이 좋고 유효성 검사에 탁월하다.

 

react-hook-form 사용 전

 

input에 useState를 생성하고 유효성 검사를 실행하는 것은 비효율적이다.

react-hook-form 에서는 이러한 문제들을 해결하고 더 좋은 로직으로 form을 구현 할 수 있다.

 

const Login = () => {
  const [ userId, setuserId ] = useState("");
  const [ userPw, setuserPw ] = useState("");

  const onChange = (e) => {
    setuserId(e.target.value);
  };

  //preventDefault 기본 동작 방지 -> submit 새로고침 막음
  const handleSubmit = (e) => {
    e.preventDefault();
  };

 return (
    <div>
      <form onSubmit={handleSubmit}>
        <h1>Login</h1>
        <input onChange={onChange} value={userId} type="text" placeholder="아이디 입력"/>
        <input type="password" placeholder="아이디 입력"/>
        <input type="submit" disabled={userId === 'user' ? "" : null/>
        <Link to="/Login"}>로그인</Link>
      </form>
    </div>
  );
}

export default Login;

 


 

react-hook-form 사용하기

 

npm install react-hook-form

 

 


 

react-hook-form 사용 후

 

register 함수를 통해 필드 등록

form에 register 함수를 통해 등록하면 react hooks form에서 validation과 submission에서 form을 통해 입력된 값 들을 사용 가능하다. 그렇기 때문에 입력을 받고자 하는 모든 필드에 반드시 register 함수를 사용해야 한다.

 

<input {...register("field_name")} />
<select {...register("field_name")}>
...

 

 

handleSubmit 함수를 통해 데이터 받기

validation이 정상적인 데이터는 handleSubmit 함수를 통해서 받을 수 있다.

handleSubmit를 사용하면 새로고침 현상이 일어나지 않는다. (event.preventDefault()를 하지 않아도 됨)

'전송'했을 때 form이 유효하다면 onValid가 실행되고, 유효하지 않는다면 onValid가 실행된다.

 

const onSubmit = (data) => console.log(data); 
<form onSubmit={handleSubmit(onSubmit)}>

 

 

validation 조건 추가

validation 조건은 register 함수의 두번 째 인자로 들어가며, 조건들은 아래와 같다.

 

required (필수 여부), 
min (최소 값), 
max(최대값), 
minLength(최소 길이), 
maxLength(최대 길이), 
pattern(정규 표현식), 
validate (custom validation 함수)

 

 

 {...register("tel", {
    required: true,
    pattern: {
        value: /^\d{3}-\d{3,4}-\d{4}$/,
        message:
            "하이픈(-)을 포함하여 전화번호를 입력해주세요",
    },
})}

 

 

validation error

userForm의 return 값에는 formState가 있고, formstate 객체 안에는 errors 객체가 포함되어 있다.

 

const {
    register, handleSubmit, formState: { errors, isValid },} = useForm({
    defaultValues,
    mode: "onChange",
});
error={!!errors?.tel}
helperText={
    errors?.tel ? errors.tel.message : null
}

 

 

실시간 유효성 검사

실시간으로 유효성 검사를 하기 위해서는 useForm에 다음과 같이 추가해야 한다.

input에 validation을 설정한 다음에 useForm에서 errors라는 객체를 가져온다.

(errors는 에러들이 담긴 객체로 현재 onChange 이기 떄문에 에러가 실시간으로 업데이트 된다.)

 

useForm({ mode: "onChange" });

 

const Login = () => {
  const { register, handleSubmit, errors } = useForm();
  
  const onSubmit = (data) => {
    console.log(data);
  };
  
  const onError = (error) => {
    console.log(error);
  };
  
  return (
    <div>
      <form onSubmit={handleSubmit(onSubmit, onError)}>
        <input
          type="text"
          placeholder="userId"
          {...register("userId", {
            minLength: {
              value: 3,
              message: "아이디를 입력해주세요."
            }
          })}
        />
        <input type="submit" />
      </form>
      {erros && <h1>{error?.userId?.message}</h1>}
    </div>
  );
}

 

 


 

Material UI를 이용한 로그인 화면 

 

const Login = () => {
    // default 값 설정
    const defaultValues = {
        tel: "",
        password: "",
    };
    
    //useForm() 구조분해할당을 이용하여 register, handleSubmit, formState를 꺼내서 사용함
    const {
        register, handleSubmit, formState: { errors, isValid },} = useForm({
        defaultValues,
        mode: "onChange",  //실시간 유효성 검사
    });

    const navigate = useNavigate();
    const onSubmit = (data: any) => console.log(data);

    return (
        <Grid container className="container" sx={{ height: "100vh" }}>
            <Grid
                className="login-left"
                item
                xs={12}
                sm={12}
                md={6}
                component={Paper}
            >
                <Link to="/">
                    <img
                        src="images/logo.png"
                        className="mainLogo"
                    />
                </Link>
                <Box
                    sx={{
                        my: 8,
                        mx: 4,
                        display: "flex",
                        flexDirection: "column",
                        alignItems: "center",
                    }}
                >
                    <Typography
                        component="h1"
                        variant="h4"
                        fontWeight={900}
                        mt={30}
                    >
                        LOGIN
                    </Typography>
                    <form onSubmit={handleSubmit(onSubmit)}>
                        <Box mt={5}>
                            <FormGroup>
                                <TextField
                                    margin="normal"
                                    fullWidth
                                    id="tel"
                                    label="전화번호를 입력해주세요 "
                                    autoComplete="tel"
                                    autoFocus
                                    {...register("tel", {
                                        required: true,
                                        pattern: {
                                            value: /^\d{3}-\d{3,4}-\d{4}$/,
                                            message:
                                                "하이픈(-)을 포함하여 전화번호를 입력해주세요",
                                        },
                                    })}
                                    error={!!errors?.tel}
                                    helperText={
                                        errors?.tel ? errors.tel.message : null
                                    }
                                />
                            </FormGroup>
                            <TextField
                                margin="normal"
                                fullWidth
                                label="비밀번호를 입력해주세요 "
                                type="password"
                                id="password"
                                autoComplete="password"
                                {...register("password", { required: true })}
                                error={!!errors?.password}
                                helperText={
                                    errors?.password
                                        ? errors.password.message
                                        : null
                                }
                            />
                            <FormControlLabel
                                control={
                                    <Checkbox
                                        value="remember"
                                        color="primary"
                                    />
                                }
                                label="로그인 상태 유지"
                            />
                            <Button
                                disabled={!isValid}
                                className="login_button"
                                type="submit"
                                fullWidth
                                variant="contained"
                                color="primary"
                                onClick={() => {
                                    navigate(Path.navi.menu);
                                }}
                            >
                                로그인
                            </Button>
                            <Typography mt={2}>
                                <Link to="#">비밀번호 찾기</Link>
                            </Typography>
                        </Box>
                    </form>
                </Box>
            </Grid>
            <Grid className="login-right" item xs={0} sm={0} md={6}>
                <img className="mainImg" />
            </Grid>
        </Grid>
    );
};

 

 


 

문제해결

 

Button에 href 를 넣으면, 유효성 검사가 제대로 실행되지 않았다.

해결 : react router dom v6 navigate (useHistory(v5) -> useNavigate(v6))

 

//해결완료

<Button
    onClick={() => {
        navigate(Path.navi.menu);
    }}
>
    로그인
</Button>

 

v5

import { useHistory } from "react-router-dom";

const App = () => {
  const history = useHistory();
  const handleClick = () => {
    history.push("/home");
  }
  
  return (
    <div>
      <button onClick={handleClick}>test</button>
    </div>
  );
}

 

v6

import { useNavigate } from "react-router-dom";

const App = () => {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate("/home");
  }
  
  return (
    <div>
      <button onClick={handleClick}>test</button>
    </div>
  );
}

 


 

<참고자료>

 

1. https://blog.toycrane.xyz/react%EC%97%90%EC%84%9C-form-%EC%89%BD%EA%B2%8C-%EB%8B%A4%EB%A3%A8%EA%B8%B0-b3b192cf2b33

 

React에서 form 쉽게 다루기

feat. React Hooks form

blog.toycrane.xyz

 

2. https://velog.io/@ryong9rrr/React-react-hook-form-%EC%98%88%EC%A0%9C

 

[React] react-hook-form 살펴보기(예제)

react-hook-form 맛만 보자

velog.io

 

반응형