세션스토리지를 이용해 로그인 유지하기 + recoil-persist

해준박·2024년 4월 26일
0
post-custom-banner

recoil을 통해 현재 로그인상태를 받아, 로그인/로그아웃의 조건부 렌더링을 하려고 하였다.

하지만 새로고침시 렌더링 과정에서 로그아웃->로그인으로 변하는것을 실시간으로 목격하였음

이는 UX부분에서 많이 별로다. 그래서 다른 방법을 찾았다. 또한, 새로고침시 recoil의 상태가 다 날아가기 때문에 이 부분도 잘 처리해야한다는 생각을 했다.

로컬스토리지 or 세션스토리지 사용하기

클라이언트 단에서 필요한 값이나 상태를 저장할 경우 로컬 또는 세션을 사용한다.

로컬스토리지와 세션스토리지의 차이
로컬 스토리지와 세션 스토리지의 차이점은 데이터의 영구성입니다. 로컬 스토리지의 데이터는 사용자가 지우지 않는 이상 계속 브라우저에 남아 있습니다. 하지만 세션 스토리지의 데이터는 윈도우나 브라우저 탭을 닫을 경우 제거됩니다. 지속적으로 필요한 데이터(자동 로그인 등)는 로컬 스토리지에 저장하고, 잠깐 동안 필요한 정보(일회성 로그인 정보라든가)는 세션 스토리지에 저장하면 되겠습니다. 하지만 비밀번호같은 중요한 정보는 절대 저장하지 마세요! 클라이언트에 저장하는 것이기 때문에 언제든지 털릴 수 있습니다.

  • 로컬스토리지는 사용자가 지우지 않는 이상 계속 브라우저에 남아 있다.
  • 세션스토리지는 윈도우 or 브라우저 탭을 닫을 경우 제거된다.

우리는 세션스토리지를 선택했다.

혹시나 중요하지않은 값이라해도 계속 보존될 경우 문제가 생길까도 생각했고, 본래 목적 자체도 새로고침시에도 유지되는 값이기에 세션스토리지를 사용해도 괜찮겠다 생각함

recoil을 이용해 조건부 렌더링을 했을 때 문제점

function LoginButton() {
  const isLogin = useRecoilvalue(isLoginAtom);

  const navigate = useNavigate();

  const navigateHandler = () => navigate("/login");

    //.. 생략

  return (
    <Button onClick={!isLogin ? navigateHandler : logoutHandler}>
      {isLogin ? <span>로그아웃</span> : <span>로그인</span>}
    </Button>
  );
}

대충 요런 느낌이었다.

(우리는 카카오 로그인을 이용했다)
카카오 로그인 성공시, useNavigate()통해 페이지를 이동하게 되는데 이 과정에서 브라우저가 새롭게렌더링 되면서 아무리 recoil을 설정해도 default값으로 돌아온다.
여기서, 1차적으로 문제점은 브라우저 새로고침 시 rcoil의 상태들은 전부 default로 날아간다.

그리고 만약,새로고침시 컴포넌트가 렌더링 되는 동시에 세션의 값을 이용해 useRecoilValue을 업데이트 하고 조건부를 했을 때,

  1. 카카오 로그인 후, 홈으로 페이지 이동
  2. isLoginAtom의 default값은 false 이며, <span>로그인</span> 렌더링
  3. 세션에 필요한 key/value가 존재할 경우 isLoginAtom = true
  4. <span>로그아웃</span> 렌더링

위 과정이 눈에 보인다. 그래서 별로 좋지 않았음 근데 아마 다른 처리하는 방법이 있을수도 있겠다. 난 몰겠음...

세션스토리지를 사용해 해결하기

결론적으로는 recoil 및 세션스토리지로 해결할 수 있었다.

// recoil/index.js
const { persistAtom: kakaoIdPeristAtom } = recoilPersist({
  key: "kakaoIdRecoilPerist",
  storage: sessionStorage,
});

export const kakaoIdAtom = atom({
  key: "kakaoId",
  default: 0,
  effects_UNSTABLE: [kakaoIdPeristAtom],
});

recoilPersist을 사용해서 storage를 추가 후 전역적으로 사용하게 하였다.

const SESSION_TIMEOUT = 30 * 60 * 1000; // 30분

function App() {
  const setKakaoId = useSetRecoilState(kakaoIdAtom);
  const setUser = useSetRecoilState(userAtom);

useEffect(() => {
    const sessionStorageKakaoId = sessionStorage.getItem("kakaoIdRecoilPerist");
    if (sessionStorageKakaoId) {
      try {
        const { kakaoId: persistedKakaoId, timestamp } = JSON.parse(
          sessionStorageKakaoId,
        );

        const now = Date.now();
        // 현재시간 - 만든시간 <= 30분
        if (now - timestamp <= SESSION_TIMEOUT) {
          setKakaoId(persistedKakaoId);
          getUserDoc(persistedKakaoId)
            .then((userDoc) => {
              if (userDoc && Object.keys(userDoc).length !== 0) {
                setUser(userDoc as IUser);
              } else {
                console.log("사용자 정보가 없습니다.");
              }
            })
            .catch((error) => {
              console.error("사용자 정보 가져오기 오류:", error);
            });
        } else {
          sessionStorage.removeItem("kakaoIdRecoilPerist");
        }
      } catch (error) {
        console.error("세션스토리지 파싱 에러:", error);
        sessionStorage.removeItem("kakaoIdRecoilPerist");
      }
    }
  }, []);

  return <RouterProvider router={router} />;
}

export default App;

app.js가 최상위라 여기다 추가하였다.

kakaoId로 로그인 유무를 판별할 수 있게되었다.
30분이 지나면 파기 할 수 있게 하고 여러가지 예외처리도 추가하였다.

개선해야 할 점

  • 더 좋은 코드 찾아보기
  • 로그인 유무를 체크하는 컴포넌트를 그냥 만들어도 될 듯 하다.
profile
기록하기
post-custom-banner

0개의 댓글