Go to main content
Development/Frontend

[Navbar] Redux-toolkit 활용: 로그인/비로그인 구분, 프로필 관리

by Nyangbari 2023. 6. 9.

회원가입 및 로그인 화면을 제외하고 나브바는 항상 좌측에 두자고 wireframe을 짰다.

처음에는 디자인 및 스타일 구현에 시간을 많이 쓰고 싶지 않아서 Matine UI를 사용하기로 팀원들과 결정을 했다.

 

처음 골랐던 Mantine Navbar UI

Vite도 지원하니 괜찮겠다 싶어서 골랐는데, 나중에 조금씩 수정하려고 보니 이 컴포넌트는 emotion을 쓰고 있었다.

우리는 styled-components를 쓰기로 한 상황이었고, 또 기존 코드에 맞춰서 수정하는 게 새로 만드는 것보다 시간이 더 걸릴 거 같았다.

그래서 디자인 참고는 하되, 그냥 새로 만들자고 결정하게 되었다. 

 

Navbar 비회원 / 회원

최상단은 비회원일 시 로그인 페이지로 가는 로그인 버튼, 회원일 시 본인 프로필 사진과 닉네임을 보여주도록 구현했다.

그리고 원래 홈화면에 넣으려고 했던 미세먼지 알림을 그 밑에 넣었다.

아래는 홈 및 카테고리들을 넣었고, 로그인된 회원의 경우에는 최하단에 로그아웃 버튼이 보이도록 했다. 

 

 

회원 / 비회원 구분하기

로그인이 되지 않은 채로 처음 홈화면으로 들어오면 프로필 사진과 닉네임 대신, 기본 사진과 로그인이라고 뜬다.

로그인이 된 유저와 비로그인 유저를 구분하는 방법으로 우리는 memberId를 사용했다.

각 회원들은 고유의 memberId를 가지고, 이 memberId가 0이면 비로그인 유저로 구분하기로 했다.

 

memberId Slice는 아래와 같다.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

const initialState = +(sessionStorage.getItem("memberId") || 0);

const memberIdSlice = createSlice({
   name: "memberId",
   initialState,
   reducers: {
      setMemberId: (_, action: PayloadAction<number>) => {
         return action.payload;
      },
   },
});

export const { setMemberId } = memberIdSlice.actions;

export default memberIdSlice.reducer;

 

로그인을 하면 memberId를 session storage에도 넣고 전역 상태도 업데이트 해주는데, 그게 아니면 initial state는 0이다.

import { RootState } from "../../store/store";

interface NavbarProfileProps {
   onClick: () => void;
}

function NavbarProfile({ onClick }: NavbarProfileProps) {
   const memberId = useSelector((state: RootState) => state.memberId);
   ...
   return memberId === 0 ? ( <>...</> ) : (<>...</>);
}

나브바에서는 이 전역 상태 memberId를 가지고 와서, memberId === 0 인지 아닌지에 따라 다른 컴포넌트를 렌더링 해주면 된다.

 

{ memberId !== 0 && ( <Logout /> ) }

로그아웃 버튼도 같은 로직으로 접근할 수 있다.

 

 

프로필 정보 받아오기

홈화면에서 나브바는 유저 프로필 사진과 닉네임을 서버로부터 받아오는데, 이를 전역 상태 관리해준다.

이로써 유저가 마이페이지로 갔을 때 보이는 상단 프로필에서는 다시 서버로부터 받아올 필요 없이 리덕스에서 꺼내 쓸 수 있다. 또한 마이페이지에서 프로필을 바꿨을 때도, 나브바에 보이는 프로필이 재빠르게 바뀔 수 있다.

 

 

nickname slice는 아래와 같다. 

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

interface ProfileNicknameState {
   nickname: string;
}
const initialState: ProfileNicknameState = {
   nickname: "",
};

const profileNicknameSlice = createSlice({
   name: "nickname",
   initialState,
   reducers: {
      setNickname: (state, action: PayloadAction<string>) => {
         return { ...state, nickname: action.payload };
      },
   },
});

export const { setNickname } = profileNicknameSlice.actions;

export default profileNicknameSlice.reducer;

 

나브바 상단 프로필 컴포넌트 전체 코드는 다음과 같다.

import { useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import userprofile from "../../assets/img/userprofile.png";
import { getUserProfile } from "../../api/axios";
import { RootState } from "../../store/store";
import { setNickname } from "../../reducers/ProfileNicknameSlice";
import { setPhoto } from "../../reducers/ProfilePhotoSlice";

interface NavbarProfileProps {
   onClick: () => void;
}

function NavbarProfile({ onClick }: NavbarProfileProps) {
   const memberId = useSelector((state: RootState) => state.memberId);
   const profilePhoto = useSelector(
      (state: RootState) => state.profilePhoto.photo
   );
   const profileNickname = useSelector(
      (state: RootState) => state.profileNickname.nickname
   );

   const { pathname } = useLocation();
   const dispatch = useDispatch();

   useEffect(() => {
      getUserProfile(memberId)
         .then((data) => {
            if (data) {
               dispatch(setNickname(data.nickname));
               dispatch(setPhoto(data.imageUrl));
            }
         })
         .catch((error) => {
            console.error("실패", error);
         });
   }, [dispatch, memberId]);

   return memberId === 0 ? (
      <NavProfileContainer to="/login" state={{ pathname }}>
         <img
            src={userprofile}
            alt="비회원 프로필 이미지"
            className="nav-profile-img"
         />
         <div className="nav-profile-nickname">로그인</div>
      </NavProfileContainer>
   ) : (
      <NavProfileContainer to="/myprofile" onClick={onClick}>
         <img
            src={profilePhoto || userprofile}
            alt="프로필 이미지"
            className="nav-profile-img"
         />
         <div className="nav-profile-nickname">{profileNickname}</div>
      </NavProfileContainer>
   );
}

 

사실 나브바보다 마이페이지 회원정보수정 페이지를 먼저 만들어서, 처음에 서버에서 받아오는 걸 마이페이지에서 했었다.

이후 나브바를 새로 내가 맡아 만들게 되었고, 생각해 보니 유저가 로그인 후 처음 마주하는 화면이 홈화면의 나브바이기 때문에 여기서 먼저 유저 정보를 서버에서 받아오는 것으로 수정했다.

 

그러고 나서 까먹은 마이페이지에 심어진 서버코드...

콘솔로그에 찍히고 있었음에도 눈치채지 못하고 프로젝트가 끝나고 나서야 발견했다. 

앞으로는 코드를 옮기거나 할 때, 새롭게 써진 코드뿐만 아니라 기존 코드도 최적화를 위해 잊지 않고 신경 써야겠다.