Context api쓰고 새로고침 후 유저 인증 유지하기

히진로그·2022년 9월 24일
1

mini-project

목록 보기
22/28
post-thumbnail

로그인이나 회원가입을 하면 더 이상 로그인, 회원가입 페이지를 보여줄 필요가 없으니까 홈 화면으로 리다이렉팅 해주는 코드를 작성했다.

이때 token정보를 받아와서 true면 홈 화면을 보여주고 false면 로그인 화면으로 이동하도록 만들었다.
개별 글을 관리하기 위해 id를 Context api에서 관리했던 것처럼 token 정보를 관리했다.

// Context를 변경해주는 useReducer함수에 login case 추가

const globalReducer = (state, action) => {
  switch (action.type) {
    case 'detail':
      return { ...state, id: action.payload };
    case 'login':
      return { ...state, token: action.payload };
    default:
      return state;
  }
};

리듀서 함수는 로그인할 때와 회원 가입할 때마다 실행해주고, type은 login, payload에는 발급받은 token 정보를 넣어주었다.

// login 커스텀 훅에 적용한 예 

export const useLogin = () => {
  const { dispatch } = useGlobalContext();
  const login = async (email, password) => {
    try {
      const response = await axios.post('http://localhost:8080/users/login', {
        email: email,
        password: password,
      });
      localStorage.setItem('token', response.data.token);
      dispatch({ type: 'login', payload: response.data.token }); // ✅
    } catch (error) {
      console.log(error);
    }
  };

  return { login };
};

token 여부에 따라 리다이렉팅을 해주기 위해 라우터를 설정해놓은 App.js에서 코드 처리를 해줬다.

mport { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { Reset } from 'styled-reset';

import Nav from './components/Nav';
import Home from './pages/Home/Home';
import Join from './pages/Join/Join';
import Login from './pages/Login/Login';
import { useGlobalContext } from './hooks/useGlobalContext';

export default function App() {
  const { token } = useGlobalContext();
  return (
    <BrowserRouter>
      <Reset />
      <Nav />
      <Routes>
        <Route
          path="/"
          element={token ? <Home /> : <Navigate replace={true} to="/login" />}
        />
        <Route
          path="/login"
          element={!token ? <Login /> : <Navigate replace={true} to="/" />}
        />
        <Route
          path="/join"
          element={!token ? <Join /> : <Navigate replace={true} to="/" />}
        />
      </Routes>
    </BrowserRouter>
  );
}

이렇게 만들고 나서 실행을 시켜보니 문제가 생겼다. 첫 로그인 또는 회원가입을 하면 홈으로 리다이렉팅이 잘되지만, 새로고침을 하면 리액트가 다시 렌더링 되어 컨텍스트에서 사용하는 값들이 모두 default값으로 초기화되고 다시 로그인 화면으로 돌아간다.

처음에는 당연하게 컨텍스트로 관리하면 내가 삭제하기 전까지 영구적으로 저장되는 게 아닌가 하고 생각했고, 초기화되는 문제를 이해하지 못했다. DB에서 무슨 무슨 로직을 사용해 로그인해서 받는 데이터를 저장하는 것도 아닌데 당연히 로그인/회원가입하면서 dispatch 함수로 업데이트시켜준 state가 새로고침해도 남아있을 거라고 생각한 내가 이젠 이해가 안감. 컨텍스트로 state를 관리하면서 자바스크립트로 데이터를 저장하면 memory에만 저장되기 때문에 새로고침 하면 초기화된다.

이 생각은 전에 리코일을 한 번 써 봤을 때도 한 생각이다. fetch를 하기 위해 필요한 데이터를 로그인할 때 리코일로 상태를 업데이트해줬더니 같은 화면에서 새로고침을 하거나 다른 state가 변경돼서 리액트가 리 렌더링 될 때마다 화면이 없어지는 현상이 생겼다.
그때 찾았던 해결방법은 recoil-persist를 사용하는 것이었는데, 리코일에서 관리하는 상태의 state값을 로컬 스토리지에 저장하면서 새로고침해도 현재 화면을 유지할 수 있도록 했었다.

여기에서는 브라우저에 메모리를 저장하는 방법으로 문제를 해결할 수 있다.

첫 번째로 생각했던 방법🤔 - 전체 페이지 라우팅을 하고 있는 App.js에서 로컬스토리지 토큰 저장여부를 useState로 관리해볼까?

이 방법으로 하다가 바로 엥하게 됨.
globalContext에서 관리하고 있는 토큰을 token 변수로 가져오고 있고, 이걸로 삼항 연산자를 사용해서 상황에 맞는 페이지로 리다이렉팅 중인데, 그럼 뭐 어떻게 삼항식을 써야 하지? 이렇게 막혀버렸음.
이렇게 되면 로컬 스토리지에서 불러온 token을 담은 변수는 남아있지만 내 코드에서 계속 지켜보고 있는 것은 Context에서 관리하고 있는 token변수이기 때문에 결국 결과는 똑같았다.

두 번째로 생각했던 방법 그리고 해결✨ - 애초에 Context에서 관리하고있는 기본 값으로 localStorage.getItem('token')을 넣어주자

현재 GlobalContext에서 기본 값으로 설정해준 token 값은 null값이다.
나의 처음 계획은 null값을 로그인/회원가입할 때 dispatch 함수를 실행해서 발급받은 token을 넣어주면서 truthy값을 사용하려고 했기 때문이다.

하지만... 새로고침을하면 다시 null값으로 초기화되기 때문에 이 계획은 물거품🛁이되었다.

하지만 애초에 기본 값을 localStorage.getItem('token')으로 넣어준다면? 얘기가 달라지지 않을까? 맞다.

const GlobalContextProvider = ({ children }) => {
  const initialToken = localStorage.getItem('token');
  const [state, dispatch] = useReducer(globalReducer, {
    token: initialToken,
    id: null,
  });

  return (
    <GlobalContext.Provider value={{ ...state, dispatch }}>
      {children}
    </GlobalContext.Provider>
  );
};

처음 로드될 때는 기본 값인 undefined가 들어올 것이고, 로그인/회원가입을 한 후 다시 새로고침을 하면 저장된 token값을 불러와서 여전히 유지가 되겠지 ㅎㅎ

1개의 댓글

comment-user-thumbnail
2023년 9월 12일

감사합니다 , 저도 비슷한 문제로 새로고침만 하면 문제가 발생했었는데 초기값을 로컬스토리지에서 받아온 값으로 하니까 해결됬네요 👨‍🌾

답글 달기