// (todoList typescript 리팩토링 과제 중 일부)
// ✅ Routes.tsx
import { createBrowserRouter } from "react-router-dom";
import Home from "../pages/Home";
import Detail from "../pages/Detail";
import Header from "../layout/Header";
import { Login } from "../pages/Login";
import { SignUp } from "../pages/SignUp";
import { ProtectedSite } from "../components/ProtectedSite";
export const router = createBrowserRouter([
{
path: "/",
element: (
// ⭐️ ProtectedSite에서 로그인 상태를 확인하여 페이지를 이동시킨다.
<ProtectedSite>
<Header />
</ProtectedSite>
),
children: [
{
path: "",
element: <Home />,
},
{
path: "detail/:id",
element: <Detail />,
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "/signUp",
element: <SignUp />,
},
]);
// ✅ ProtectedSite.tsx
import { Navigate } from "react-router-dom";
export const ProtectedSite = ({ children }: { children: React.ReactNode }) => {
// ⭐️ session storage 토큰 유무로 로그인 상태를 확인
const token = sessionStorage.getItem("token");
if (!token) {
return <Navigate to="/login" />;
}
return children;
};
// ✅ Login.tsx
import { Link, useNavigate } from "react-router-dom";
import styled from "styled-components";
import { UserLogin } from "../api/auth-api";
export const Login = () => {
const navigate = useNavigate();
const onLoginHandler = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const id = formData.get("id") as string;
const password = formData.get("password") as string;
try {
// ⭐️ 로그인시 - sessionStorage에 토큰 저장, 홈으로 이동
// try catch문으로 작성해야 오류처리 가능!
const data = await UserLogin({ id, password });
sessionStorage.setItem("token", data.accessToken);
navigate("/");
} catch (error) {
console.log("error", error);
}
};
return (
<Container>
<Form onSubmit={onLoginHandler}>
<p>☁️ 로그인 ⛅️</p>
<input type="text" name="id" placeholder="email" />
<input type="password" name="password" placeholder="비밀번호" />
<input type="submit" value="로그인" />
<div>
<span>회원이 아니신가요? → </span>
<LinkToSignUp style={{ cursor: "pointer" }} to="/signUp">
회원가입하기
</LinkToSignUp>
</div>
</Form>
</Container>
);
};
// ✅ auth-api.ts - 로그인 request보내는 api
import axios from "axios";
import { User } from "../types/Auth";
const userClient = axios.create({
baseURL: "https://moneyfulpublicpolicy.co.kr",
headers: {
"Content-Type": "application/json",
},
});
interface signUpProps {
id: string;
password: string;
nickname: string;
}
type LoginProps = Omit<signUpProps, "nickname">;
export const UserLogin = async ({
id,
password,
}: LoginProps): Promise<User> => {
const response = await userClient.post("/login", {
id,
password,
});
return response.data;
};
문제 : 로그인하면 바로 session storage를 확인해서 토큰이 있으면 홈화면으로 넘겨줘야하는데, 토큰 확인을 안해서 홈화면으로 넘어가질 못하고 로그인화면에서 제자리
기존 코드
// 🧡 시도 1. useEffect 활용하여 화면 렌더링시 token 판별하게 만들기
import { useEffect, useState } from "react";
import { Navigate } from "react-router-dom";
export const ProtectedSite = ({ children }: { children: React.ReactNode }) => {
const [token, setToken] = useState(sessionStorage.getItem("token"));
// 의존성 배열에도 token, sessionStorage.getItem("token") 등 다 넣어봄
useEffect(() => {
const getToken = sessionStorage.getItem("token");
getToken && setToken(getToken);
}, []);
// 여기서 token이 빈값으로 뜸 (undefined, null 아닌 그냥 아예 빈칸)
console.log("token", token);
if (!token) {
return <Navigate to="/login" />;
}
return children;
};
// 🧡 시도 2. useEffect 제거 후 재시도 - Too many rerender 오류 발생
import { useEffect, useState } from "react";
import { Navigate } from "react-router-dom";
export const ProtectedSite = ({ children }: { children: React.ReactNode }) => {
const [token, setToken] = useState(sessionStorage.getItem("token"));
const getToken = sessionStorage.getItem("token");
getToken && setToken(getToken);
if (!token) {
return <Navigate to="/login" />;
}
return children;
};
원인
시도 1
: useEffect는 마운트 된 다음 실행되는 hook이다. 따라서 아래의 console.log와 if문이 실행된 다음에야 useEffect가 실행되는 흐름이었던 것! 로그인페이지로 Navigate 한 다음 토큰을 판별하니까 로그인페이지에 계속 머물러있었다...!
-> 마운트와 hook의 실행에 대한 흐름을 알아야 했다.
시도 2
: useState로 set하면 화면이 리렌더링된다. 해당 로직에서는 토큰이 있으면 setToken하고, set했으니 리렌더링 발생하고, 리렌더링해보니 토큰이 있으니까 또 set하고, set했으니까 리렌더링되고... 무한 반복이었던 것이다.
-> useState set하는 경우 화면이 리렌더링되는 흐름을 이해해야 했다.
해결 코드 : 그냥 session storage에서 토큰 가져오는 변수 지정하고 토큰 있으면 페이지 이동... 단순하게 하면 되는 거였다...!
import { Navigate } from "react-router-dom";
export const ProtectedSite = ({ children }: { children: React.ReactNode }) => {
const token = sessionStorage.getItem("token");
if (!token) {
return <Navigate to="/login" />;
}
return children;
};