React+REST API 게시판 구현/FE - React

회원가입 구현하기

덕구공 2021. 9. 29. 20:27

What to do?

  • meterial-ui의 기본적인 input을 가져다 쓰고 formik을 통해서 form의 state 관리를 하고 yup을 사용해서 form의 validation을 검증하자.
  • form의 validation 검증이 끝나면 axios로 회원가입을 처리하는 API에 POST 방식으로 데이터를 보내서 서버에 유저의 정보를 저장한다. 
  • 사용 url: /sign-up
  • 회원가입에 성공하면 react-toastify로 2초동안 toast 알림창을 띄우고 /login 으로 이동하게 하자!

  • 회원가입 성공 시 DB에 반영이 되는 모습이다!

회원가입 페이지 구현

  • pages 디렉토리 아래 sign-up 디렉토리를 만들고 Sigup.js 파일과 singUp.scss 파일을 만들자.

pages/sign-up/SignUp.js

  • formik과 yup으로 form의 state 관리 및 유효성 검증을 하고 회원가입을 하는 API 호출 시, 오류가 없으면 toast 알림을 2초간 띄운 뒤 /login (로그인 페이지)으로 이동시키자
  • 그리고 Formik의 validOnMount를 사용하여 마운트가 되자마자 error message를 띄워보도록 하자!
  • yup의 validationSchema를 정의할 때 regax라는 문법을 사용하는데 이를 사용하면 매우매우 편리하다 ㅎㅎ
import axios from "axios";
import {toast, ToastContainer} from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import {Formik, ErrorMessage} from "formik";
import * as Yup from "yup";
import {Button, TextField} from "@mui/material";
import {useNavigate} from "react-router-dom";
import "./signUp.scss";

const SignUp = () => {
  const navigate = useNavigate();
  const validationSchema = Yup.object().shape({
    email: Yup.string()
      .email("올바른 이메일 형식이 아닙니다!")
      .required("이메일을 입력하세요!"),
    username: Yup.string()
      .min(2, "닉네임은 최소 2글자 이상입니다!")
      .max(10, "닉네임은 최대 10글자입니다!")
      .matches(
        /^[가-힣a-zA-Z][^!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\s]*$/,
        "닉네임에 특수문자가 포함되면 안되고 숫자로 시작하면 안됩니다!"
      )
      .required("닉네임을 입력하세요!"),
    password: Yup.string()
      .min(8, "비밀번호는 최소 8자리 이상입니다")
      .max(16, "비밀번호는 최대 16자리입니다!")
      .required("패스워드를 입력하세요!")
      .matches(
        /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])[^\s]*$/,
        "알파벳, 숫자, 공백을 제외한 특수문자를 모두 포함해야 합니다!"
      ),
    password2: Yup.string()
      .oneOf([Yup.ref("password"), null], "비밀번호가 일치하지 않습니다!")
      .required("필수 입력 값입니다!"),
  });
  const submit = async (values) => {
    const {email, username, password} = values;
    try {
      await axios.post("/api/auth/signup", {
        email,
        username,
        password,
      });
      toast.success(<h3>회원가입이 완료되었습니다.<br/>로그인 하세요😎</h3>, {
        position: "top-center",
        autoClose: 2000
      });
      setTimeout(()=> {
        navigate("/login");
      }, 2000);

    } catch (e) {
      // 서버에서 받은 에러 메시지 출력
      toast.error(e.response.data.message + "😭", {
        position: "top-center",
      });
    }
  };

  return (
    <Formik
      initialValues={{
        email: "",
        username: "",
        password: "",
        password2: "",
      }}
      validationSchema={validationSchema}
      onSubmit={submit}
      validateOnMount={true}
    >
      {({values, handleSubmit, handleChange, errors}) => (
        <div className="signup-wrapper">
          <ToastContainer/>
          <form onSubmit={handleSubmit} autoComplete="off">
            <div className="input-forms">
              <div className="input-forms-item">
                <div className="input-label">이메일</div>
                <TextField
                  value={values.email}
                  name="email"
                  variant="outlined"
                  onChange={handleChange}
                />
                <div className="error-message">
                  {errors.email}
                </div>
              </div>
              <div className="input-forms-item">
                <div className="input-label">닉네임</div>
                <TextField
                  value={values.username}
                  name="username"
                  variant="outlined"
                  onChange={handleChange}
                />
                <div className="error-message">
                  {errors.username}
                </div>
              </div>
              <div className="input-forms-item">
                <div className="input-label">비밀번호</div>
                <TextField
                  value={values.password}
                  name="password"
                  variant="outlined"
                  type="password"
                  onChange={handleChange}
                />
                <div className="error-message">
                  {errors.password}
                </div>
              </div>
              <div className="input-forms-item">
                <div className="input-label">비밀번호 확인</div>
                <TextField
                  value={values.password2}
                  name="password2"
                  variant="outlined"
                  type="password"
                  onChange={handleChange}
                />
                <div className="error-message">
                  {errors.password2}
                </div>
              </div>
              <Button
                color="primary"
                variant="contained"
                fullWidth
                type="submit"
              >
                회원가입
              </Button>
            </div>
          </form>
        </div>
      )}
    </Formik>
  );
};

export default SignUp;

pages/signUp/signUp.scss

  • rem을 이용해서 input form의 글씨 크기를 반응형으로 만들고 미디어 쿼리를 사용해서 input의 너비를 반응형으로 만들자!
@media all and (min-width: 1024px) {
  .input-forms {
    padding: 70px;
    input{
      width: 400px;
    }
  }

}
@media all and (max-width: 1024px) {
  .input-forms {
    padding: 60px;
    input{
      width: 330px;
    }
  }
}

@media all and (max-width: 768px) {
  .input-forms {
    padding: 50px;
    input{
      width: 300px;
    }
  }
}
.signup-wrapper {
  font-family: 'Noto Sans KR', sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;

  .input-forms {
    background-color: #ffffff;
    border-radius: 10px;
    display: flex;
    flex-direction: column;

    .input-label {
      font-size: 1.2rem;
      font-weight: bold;
      color: #282c34;
    }

    .input-forms-item {
      padding: 7px 0;
      .error-message {
        font-size: 1rem;
        color: blue;
        font-weight: bold;
      }
    }
  }
}

라우팅 추가하기

App.js

  • /sign-up url로 진입했을 때 App.js에서 SignUp 컴포넌트를 보여주자
import React from "react";
import {Routes, Route} from "react-router-dom"
import Header from "./components/Header";
import Home from "./pages/home/Home";
import SignUp from "./pages/sign-up/SignUp";

const App = () => {
  return (
    <React.Fragment>
      <Header/>
      <Routes>
        <Route path="/" element={<Home/>}/>
        <Route path="/sign-up" element={<SignUp/>}/>
      </Routes>
    </React.Fragment>
  )
}
export default App