본문 바로가기
프론트엔드로 가는 길/portfolio 제작 여정기 👩🏻‍💻

05. 자살예방웹사이트 - 로그인 문제

by woody-j 2023. 9. 4.

문제 상황:

액세스 토큰은 저장 및 삭제에는 문제가 없지만, 로그인 후 로그아웃을 하더라도 로그인 텍스트가 즉시 변경되지 않는다는 문제가 발생했다.

 

로그인 기능을 구현하기 위해 액세스 토큰을 변수에 저장하는 방식을 선택했다.

이렇게 토큰을 저장하고 사용하려고 했지만, 실제로 이 방식은 내부적으로 클로저를 사용하는 것과 같다는 것을 깨달았다. 

저것이 문제였다. 

데이터 내용이 바뀌어도 업데이트를 감지를 하지못한다.
즉, 상태를 직접 관리하는 방식은 컴포넌트 간의 상태 공유를 어렵게 만들고, 상태 업데이트를 추적하고 디버깅하기 어렵다.

그래서 리덕스를 사용했다.

 

 

리덕스를 사용하면 상태 업데이트와 상태 관리를 예측 가능하게 만들어준다.

또한 컴포넌트 간의 상태 공유를 용이하게 하며, 상태 변경 사항을 추적하고 디버깅하기 쉽다.

따라서 리덕스를 사용하면 복잡한 애플리케이션에서도 일관된 상태 관리를 유지할 수 있다.

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

const initialState = "";

export const userLoginAccessTokenSlice = createSlice({
  name: "userLoginAccessToken",
  initialState,
  reducers: {
    setUserLoginAccessTokenSlice: (state, action: PayloadAction<any>) => {
      return action.payload;
    },
  },
});

export let { setUserLoginAccessTokenSlice } = userLoginAccessTokenSlice.actions;

 

import React, { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import styled from "styled-components";
import { FlexRowCenterDiv, FlexRowDiv } from "../../module/styled/FlexDiv";
import { FaRegWindowMinimize, FaRegWindowMaximize } from "react-icons/fa";
import { CgClose } from "react-icons/cg";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { setUserLoginAccessTokenSlice } from "../../store/reducer/userData/userData/userLoginAccessTokenSlice";
import { setUserLoginDataSlice } from "../../store/reducer/userData/userData/userLoginDataSlice";

const NavWrapper = styled(FlexRowCenterDiv)`
  padding: 0 20px;
  position: fixed;
  top: 0;
  justify-content: space-between;
  width: 100%;
  z-index: 1000;
  border: 2px solid ${({ theme }) => theme.color.mainBlack};
  border-right: none;
  border-left: 0;
  background-color: ${({ theme }) => theme.color.mainWhite};
  font-size: 1.8em;
  font-weight: 700;
  @media screen and (max-width: 768px) {
    flex-direction: column;
    align-items: center;
    padding: 10px;
  }
  @media screen and (max-width: 375px) {
    padding: 0;
  }
`;

const Menus = styled(FlexRowDiv)``;

const MovePage = styled(Link)`
  padding: 15px 20px;
  transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
  cursor: pointer;
  text-decoration: none;

  &.active {
    background-color: ${({ theme }) => theme.color.mainBlack};
    color: ${({ theme }) => theme.color.mainWhite};
  }

  &:hover {
    background-color: ${({ theme }) => theme.color.hover};
    color: ${({ theme }) => theme.color.mainWhite};
  }

  @media screen and (max-width: 768px) {
    padding: 10px 15px;
  }
`;

const RightBox = styled(FlexRowDiv)`
  align-items: center;
  gap: 20px;
`;

const IconBox = styled(FlexRowDiv)`
  gap: 15px;
  align-items: center;
  font-size: 2.3rem;

  @media screen and (max-width: 768px) {
    display: none;
  }
`;

const Login = styled.div`
  font-size: 2rem;
`;
const Logout = styled.div`
  font-size: 2rem;
  cursor: pointer;
`;

const Nav = () => {
  const [selectedMenu, setSelectedMenu] = useState<number>();
  const location = useLocation(); // 현재 URL 경로를 가져오기 위한 Hook
  const dispatch = useDispatch();
  const accessToken = useSelector(
    (state: { userLoginAccessTokenSlice: any }) => state.userLoginAccessTokenSlice
  );
  const handleMenuClick = (index: number) => {
    setSelectedMenu(index);
  };

  const menuItems = [
    { to: "/", label: "Home" },
    { to: "/test", label: "Test" },
    { to: "/post", label: "Post" },
    { to: "/letter", label: "Letter" },
    { to: "/information", label: "Info" },
  ];

  const handleLogIn = () => {
    setSelectedMenu(undefined);
  };

  const handleLogout = () => {
    dispatch(setUserLoginDataSlice({ uid: "", userEmail: "", authToken: "" }));
    dispatch(setUserLoginAccessTokenSlice(""));
    console.log("로그아웃이 완료 되었습니다.");
  };

  useEffect(() => {
    // 현재 URL 경로에 따라 selectedMenu 초기화
    const pathname = location.pathname;
    const matchingIndex = menuItems.findIndex((item) => item.to === pathname);

    if (matchingIndex !== -1) {
      setSelectedMenu(matchingIndex);
    }
  }, [location]);

  return (
    <NavWrapper>
      <Menus>
        {menuItems.map((el, index) => (
          <MovePage
            key={index}
            to={el.to}
            className={selectedMenu === index ? "menu active" : "menu"}
            onClick={() => {
              handleMenuClick(index);
            }}
          >
            {el.label}
          </MovePage>
        ))}
      </Menus>
      <RightBox>
        {accessToken ? (
          <Logout onClick={handleLogout}>Logout</Logout>
        ) : (
          <Login onClick={handleLogIn}>
            <Link to="/auth/signIn">Login</Link>
          </Login>
        )}
        <IconBox>
          <FaRegWindowMaximize />
          <CgClose style={{ fontSize: "3rem" }} />
        </IconBox>
      </RightBox>
    </NavWrapper>
  );
};
export default Nav;