[Personal Project] 로그인/회원가입 기능이 추가된 가계부 (1)

liinyeye·2024년 6월 12일
0

Project

목록 보기
16/44
post-thumbnail

로그인 / 회원가입 로직

처음에 작성한 로직

처음에는 회원 가입한 유저와 로그인한 유저의 상태를 따로 관리해줘야한다고 생각했다. 아직 useState로 상태관리를 한다는 개념이 데이터를 저장하고 불러와 사용한다는 개념이랑 헷갈려서 그렇게 생각했던 것 같다.

하지만 작성하다보니 구지 이 둘의 상태를 나눠서 관리할 필요가 없으며 마찬가지로 회원가입 시에 만들어진 유저의 상태를 관리할 필요도 없다고 깨달았고, const [users, setUsers] = useState(null); 로만 유저의 상태관리를 해주는 것으로 변경했다.

다만 여기서 문제점은 로그인 여부가 전역적으로 상태관리가 필요한데 현재 내가 작성한 컴포넌트는 최상단 컴포넌트도 아니었을 뿐더러 부모-자식 관계로 해당 상태를 props로 내려줄 수도 없는 상황이라 contextAPI나 redux로 user의 상태 관리를 리팩토링 해줘야한다는 필요성을 느꼈다.

또한, 로그인 여부를 확인하고 이 상태를 가지고 다른 로직들을 작성해줘야하기 때문에 로그인 시 유저의 액세스토큰을 저장해주는 작업이 필요했다.

상태관리

  const [formData, setFormData] = useState({ id: '', password: '', nickname: '', isLogin: false });
  const [users, setUsers] = useState(null);
  const [loginUsers, setLoginUsers] = useState(null);

회원가입

  // 회원가입 api 로직
  const handleRegister = async (user) => {
    const { data } = await api.post('/register', user);
    setUsers([...users, data]);
  };
  
  // 회원가입 폼 제출
  const onSubmitRegister = (event) => {
    event.preventDefault();
    
    // 유효성 검사
    // 코드 생략

    handleRegister({ ...formData, isLogin: true });
    navigate('/home');
    setFormData({ id: '', password: '', nickname: '' });
  };

로그인

// 로그인 api 로직
  const handleLogin = async (user) => {
    try {
      const { data } = await api.post('/login', {
        id: user.id,
        password: user.password
      });
      console.log(user);
      if (data.success) {
        toast.success('로그인 성공!');
        setLoginUsers({ id: userId, nickname: nickname });
        navigate('/home');
      } else {
        toast.error('로그인에 실패했습니다.');
      }
    } catch (error) {
      console.log('Error logging in', error);
      toast.error('로그인 실패: 서버 오류입니다.');
    }
  };

// 로그인 폼 제출
  const onSubmitLogin = (event) => {
    event.preventDefault();
    if (!formData.id || !formData.password) {
      toast.warn('이메일과 비밀번호를 입력해주세요.');
      return;
    }
    handleLogin(formData);
    setFormData({ id: '', password: '', nickname: '' });
  };

수정한 로직 (리팩토링)

  • api를 불러오는 로직이 함수 컴포넌트 안에 있으면 리랜더링이 일어날 때마다 api가 호출되기 때문에 api를 따로 캡슐화(?)해서 관리
  • 전역적으로 user 상태관리를 할 수 있도록 리덕스 툴킷으로 리팩토링
  • 로컬 스토리지에 accessToken을 저장하여 유저의 로그인 상태를 구독할 수 있도록 함

로그인과 회원가입까지는 강의 내용을 보며 어느정도 로직을 작성해볼 수 있었는데, 유저의 로그인 상태를 구독하는 비동기 함수를 작성할 때는 headers에 추가로 작성해주는 부분이 있었고 이 부분에 대한 개념이 잘 이해가 되지 않아 해설 강의를 보며 코드를 작성했다. axios의 매서드를 사용할 때 경우에 따라 인자에 필요한 정보를 넣어준다는 것을 알게 됐다.

axios.get(url[, config])

여기서 config 부분이 { headers: { Authorization: Bearer ${accessToken} }

  • 추가로 알게 된 내용
    [,config] : 선택적 매개변수 표기법 의미
    -> 대괄호 : 선택,
    -> config(객체) : 써도 되고, 안써도 된다,
    -> ‘,’앞에 콤마가 있는 이유 : 2번째 인자

상태관리

redux Toolkit

import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'users',
  initialState: {
    user: null
  },
  reducers: {
    setUser: (state, action) => {
      state.user = action.payload;
    }
  }
});

export const { setUser } = userSlice.actions;
export default userSlice.reducer;

회원가입

// 회원가입 api 로직
export const register = async ({ id, password, nickname }) => {
  try {
    const response = await authApi.post('/register', {
      id: id,
      password: password,
      nickname: nickname
    });
    // console.log('회원가입 데이터 확인', response.data);
    return response.data;
  } catch (error) {
    console.log(error?.response?.data?.message);
    toast.warn(error?.response?.data?.message);
  }
};
  // 회원가입
  const onSubmitRegister = async (event) => {
    event.preventDefault();

    // 유효성 검사 코드 생략

    const response = await register({ ...formData });
    if (response) {
      setFormData({ id: '', password: '', nickname: '' });
      toast.success('회원가입이 완료되었습니다.');
    }
  };

로그인

// 로그인 api 로직
export const login = async ({ id, password }) => {
  try {
    const response = await authApi.post('/login?expiresIn=30m', {
      id: id,
      password: password
    });
    console.log('로그인 데이터 확인', response.data);
    localStorage.setItem('accessToken', response.data.accessToken);
    return response.data;
  } catch (error) {
    console.log(error?.response?.data?.message);
    toast.warn(error?.response?.data?.message);
  }
};
  // 로그인
  const onSubmitLogin = async () => {
    const { userId, nickname, avatar } = await login({
      id: formData.id,
      password: formData.password
    });
    toast.success('로그인 성공!');
    dispatch(setUser({ userId, nickname, avatar }));
    navigate('/home');
  };

로그인 된 유저 구독

export const getUserInfo = async () => {
  const accessToken = localStorage.getItem('accessToken');
  if (accessToken) {
    try {
      const response = await authApi.get('/user', {
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${accessToken}`
        }
      });
      return response.data;
    } catch (error) {
      localStorage.clear();
      toast.warn('accessToken이 만료되었습니다.');
    }
  }
};

참고 자료

profile
웹 프론트엔드 UXUI

0개의 댓글