React 로그인 구현하기

피시본·2022년 10월 25일
3

리액트로 로그인 폼 구현하기

로그인 유저만 이용할 수 있는 서비스가 있었기에 rest api를 연결할 부분도 내가 짜게 되었다. 1차 프로젝트에서는 학원에서 짜준 로직을 이해도 못하고 그냥 import로 끌어다 쓰기만 해서 아쉬웠는데 이번에는 내가 처음부터 끝까지 짜볼 수 있단 생각에 두근거렸다.

먼저 axios를 사용할지 fetch를 사용할지부터 정해야 했다.

  • fetch
    : 자바스크립트 내장 함수여서 별도의 설치가 필요없다.
    : 응답 받은 데이터를 .then(res => res.json()) 이런 식으로 중간에 json화 해주어야 한다.
    : 프로미스 객체를 리턴하므로 코드 보기가 꽤 직관적이다.
  • axios
    : 데이터를 json화 해준다.
    : 따로 설치해야 한다.
    : axios를 처음 써보는 사용자는 fetch와 구별되는 axios의 장점을 살리지 못할 수 있다.

우리 팀은 장단점 비교와 서치 정보량을 통해 최종 결정을 axios로 정하게 되었다. 찾아 보니 많은 개발자가 axios를 선호하기도 하고 우리 팀은 CRA 말고 VITE를 사용했는데 서치 결과 비트 사용자가 엑시오스를 더 이용하길래 선택했다.😅

axios를 사용해보니 . . .

fetch에 비해 코드를 많이 줄일 수 있다. then으로 내려가며 코드가 실행되는 fetch와 달리 axios는 동일한 조건을 앞에 명시해주면 뒤에 이어지는 코드에는 생략해도 같은 결과를 낼 수 있다.

/utils/Api.jsx

import axios from "axios";

axios.defaults.timeout = 3000;
axios.defaults.headers["Content-Type"] = "application/json";
const backendPortNumber = import.meta.env.VITE_BACK_PORT_NUM;
const BASE_URL = `http://${window.location.hostname}:${backendPortNumber}/`;

axios.interceptors.response.use(
    (res) => {
      return res.data;
    },
    (err) => {
      console.log(err);
      throw new Error("(!) axios error");
    }
  );

// 이 기능은 사용하지 않았기에 주석 처리 해둔다.
// axios.interceptors.request.use(
//     (req) => {
//         console.log(req.headers, req.body);
//         // express로 보내는 데이터 타입이 "이미지 형태"일 경우, 
//         // "Content-Type = multipart/form-data";
//     },
//     (err) => {
//         console.log(err);
//     }
// )
  
const customAxios = axios.create({
    headers: {
        Authorization: `Bearer ${sessionStorage.getItem("userToken")}`,
    }
  });

const post = async (endpoint, data) => {
    const bodyData = JSON.stringify(data);

    return customAxios.post(BASE_URL + endpoint, bodyData);
}

const get = async (endpoint, params="") => {
    return customAxios.get(BASE_URL + endpoint + "/" + params);
}
...(중략)

이런 식으로 코드를 정리해서 앞에 달아 놓으면 axios가 뒤에 나오는 코드를 알아서 이어준다. 알고 보니 보기 훨씬 간편하고 깔끔하단 생각이 들었다.


본격적인 로그인 구현

로그인 여부를 정의해줄 리듀서 함수부터 먼저 만들었다.

export const loginReducer = (userState, action) => {
    switch (action.type) {
        //action type이 "LOGIN"
        case "LOGIN":
            return {
                ...userState,
                user: action.payload,
            };
        case "LOGOUT":
            return {
                ...userState,
                user: null,
            }
        default:
            return userState;
    }
}

createContext 함수를 사용해 app.jsx에 사용자의 로그인 여부(state)와 dispatch 함수를 정의했다.

export const UserStateContext = createContext(null);
export const DispatchContext = createContext(null);

회원가입 폼에서 쓰인 유효성 검사가 할 게 많다는 생각에 로그인 폼에도 유효성 검사를 넣었다.

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

const [emailMsg, setEmailMsg] = useState("");
const [pwdMsg, setPwdMsg] = useState('');
    
    
const validateEmail = (email) => {
        return email
          .toLowerCase()
          .match(/([\w-.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/);
      };

const validatePwd = (password) => {
        return password
          .toLowerCase()
          .match(/^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{10,25}$/);
      };
      
      
const onChangeEmail = useCallback((e) => {
    const currEmail = e.target.value;
    setEmail(currEmail);
    
   if (!validateEmail(currEmail)) {
      setEmailMsg("이메일 형식이 올바르지 않습니다.")} 
  else { setEmailMsg("") }
    })
    
   const onChangePwd = useCallback((e) =>{
      const currPwd = e.target.value;
      setPassword(currPwd);
    
      if (!validatePwd(currPwd)) {
        setPwdMsg("영문, 숫자, 특수기호 조합으로 10자리 이상 입력해주세요.")
      } else {
        setPwdMsg("")
      }
    }, [])
    
    
// 유효성 검사로 걸러주기 위해 변수로 넣어준다. return에서 삼항 연산자로 쓰인다.
const isEmailValid = validateEmail(email);
const isPwdValid = validatePwd(password);

// 회원가입과 같이 유효성 검사가 통과되어야만 버튼 활성화가 된다.
const isAllValid = isEmailValid && isPwdValid;

입력한 메일 주소나 비밀번호가 틀렸을 때도 곧바로 문구를 띄워 주기로 했다. 백엔드에서 errorCause라는 이름으로 틀린 이메일과 비밀번호를 걸러주기에 이 부분을 이용해서 입력한 이메일과 비밀번호가 틀렸다고 안내해주기로 했다.
const dispatch = useContext(DispatchContext);

const [login, setLogin] = useState(true);

const [checkEamil, setCheckEmail] = useState(false);
const [checkPwd, setCheckPwd] = useState(false);

const onSubmit = async (e) => {
        e.preventDefault();

      try {
        const res = await Api.post("user/login", {
          email, 
          password,
        })

        const user = res.data;
        const jwtToken = user.token;
        const { result, errorCause } = res.data;

        // 토큰 저장
        sessionStorage.setItem("userToken", jwtToken);

        dispatch({
          type: "LOGIN",
          payload: user,
        });
        
        // 틀린 이메일, 비밀번호 걸러주기
        if (!result) {
          if (errorCause === "email") {
            setEmailMsg("입력하신 이메일이 존재하지 않습니다. 다시 입력해주세요.")
            setCheckEmail(false);
          } else if (errorCause === "password") {
            setPwdMsg("입력하신 비밀번호가 존재하지 않습니다. 다시 입력해주세요.")
            setCheckPwd(false);
          }
        } else {
          setCheckEmail(true);
          setCheckPwd(true);
          navigate("/", { replace: true });
        }
      } catch (err) {
        setLogin(false);
        console.log("로그인 실패\n" , err)
      }
      }

이렇게 로그인 구현 성공!

배운 점

백엔드와의 상호작용이 중요하단 걸 느꼈다. 틀린 이메일과 비밀번호를 어떻게 잡아줘야 할지 헷갈리고 여러 시행착오도 많이 겪고 고민도 많았는데(아직 리액트를 잘 못 다루고 흐름이 어떻게 되는 건지 갈피를 못 잡을 때가 있다😢) 백엔드 쪽에서 errorCause를 딱 명시해주니 화면에 띄우기 훨씬 쉬웠다. 코치님이 알려주신 팁이었는데 물어보길 잘했다는 생각이다. 감사합니다.

다음은 ... 말도 많고 탈도 많았던 카카오톡 간편 로그인 구현에 대해 적어보기로 한다.

profile
Hello, World!

0개의 댓글