원래 이런 사람이 아닌데 이번 프로젝트때는 왜 이렇게 놓친게 많은것인지...
안그래도 공통 컴포넌트인 Header
를 만들고 사용해야하는 페이지마다 계속해서 넣어주는것이 너무 불편했는데 이미 이런 가이드가 존재하고 있었다...
매우 반성한다.
반성은 반성이고 할건하자!
components
폴더내에 파일을 생성하고,import { Outlet } from "react-router-dom";
import Header from "./Header";
const Layout = () => {
return (
<>
<Header />
<Outlet />
</>
);
};
export default Layout;
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Profile from "./pages/Profile";
import Signup from "./pages/Signup";
import TestPage from "./pages/TestPage";
import TestResult from "./pages/TestResult";
import ProtectedRoute from "./components/ProtectedRoute";
import Layout from "./components/Layout";
const AppRouter = () => {
return (
<Router>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route element={<ProtectedRoute />}>
<Route path="/profile" element={<Profile />} />
<Route path="/test" element={<TestPage />} />
<Route path="/results" element={<TestResult />} />
</Route>
</Route>
</Routes>
</Router>
);
};
export default AppRouter;
Router.jsx
에서 감싸주면 끝!!!Layout
을 만들고나니 문제가 하나 발생했다.
이슈 :
로그인정보가 전역적으로 관리되지 않아서 헤더부분의 상태가 리렌더링 되지 않는 버그가 발생했다.
이 버그를 해결하기위해서 미루고 미뤘던 ContextAPI
를 강제적으로 사용하게 됐다.
src
폴더내 위와 같이 생성한뒤,import { createContext, useContext, useState, useEffect } from "react";
import { login as apiLogin } from "../api/auth";
import { toast } from "react-toastify";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState(null);
// 로그인 함수
const login = async (userid, password) => {
try {
const { userId, nickname, token } = await apiLogin(userid, password);
// 상태 업데이트
setIsLoggedIn(true);
setUser({ userId, nickname });
return { userId, nickname, token }; // 필요 시 반환
} catch (error) {
console.error("Login failed in AuthContext:", error);
throw error;
}
};
// 로그아웃 함수
const logout = () => {
localStorage.removeItem("user");
localStorage.removeItem("token");
toast.success("로그아웃이 완료되었습니다.");
setIsLoggedIn(false);
setUser(null);
};
// 초기 사용자 상태 설정
useEffect(() => {
const storedUser = JSON.parse(localStorage.getItem("user"));
if (storedUser) {
setIsLoggedIn(true);
setUser(storedUser);
}
}, []);
return (
<AuthContext.Provider value={{ isLoggedIn, user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
AuthContext.jsx
의 로직을 작성해준다.
주의사항!!!
이미 auth.js
파일에 login
로직이 있으므로, 반드시 그것을 갖다써야한다.
import
부분을 보면 로그인 함수를 auth.js
에서 가져온것을 볼 수 있다.
import AppRouter from "./Router";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { AuthProvider } from "./contexts/AuthContext";
function App() {
return (
<AuthProvider>
<AppRouter />
<ToastContainer/>
</AuthProvider>
);
}
export default App;
App.jsx
를 감싸주고난 뒤,//Header.jsx
import { Link } from "react-router-dom";
import LoggedInNav from "./LoggedInNav";
import LoggedOutNav from "./LoggedOutNav";
import { useAuth } from "../contexts/AuthContext";
const Header = () => {
const { isLoggedIn } = useAuth();
return (
<header className="bg-gray-100 shadow-md fixed top-0 left-0 w-full z-50">
<div className="container mx-auto flex justify-between items-center h-16 px-6">
<div>
<Link
to="/"
className="text-gray-800 hover:text-red-600 font-semibold text-lg"
>
홈
</Link>
</div>
<div className="flex items-center space-x-6">
{isLoggedIn ? <LoggedInNav /> : <LoggedOutNav />}
</div>
</div>
</header>
);
};
export default Header;
useAuth
를 사용해서 받아올 수 있다.//Login.jsx
import { useNavigate, Link } from "react-router-dom";
import AuthForm from "../components/AuthForm";
import { useAuth } from "../contexts/AuthContext";
import { toast } from "react-toastify";
const Login = () => {
const navigate = useNavigate();
const { login } = useAuth(); // AuthContext의 login 함수 사용
const handleLogin = async (formData) => {
try {
const { nickname } = await login(formData.userid, formData.password); // AuthContext의 login 호출
toast.success(`${nickname}님, 환영합니다!`);
navigate("/"); // 로그인 후 홈으로 이동
} catch (error) {
console.error("로그인 실패:", error.response?.data || error.message);
toast.error("로그인에 실패했습니다. 아이디와 비밀번호를 확인해주세요.");
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="bg-white shadow-lg rounded-lg p-8 w-96">
<h1 className="text-2xl font-bold text-gray-800 text-center mb-6">
로그인
</h1>
<AuthForm mode="login" onSubmit={handleLogin} />
<p className="text-center text-sm text-gray-500 mt-4">
계정이 없으신가요?{" "}
<Link to="/signup" className="text-red-500 font-bold hover:underline">
회원가입
</Link>
</p>
</div>
</div>
);
};
export default Login;
login
함수를 useAuth
에서 가져오는것이 포인트!!!//ProtectedRoute.jsx
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
const ProtectedRoute = () => {
const { user } = useAuth();
const isAuthenticated = !!user; // 사용자 데이터가 존재하면 인증됨
// 인증 여부에 따라 Outlet 또는 로그인 페이지로 리다이렉트
return isAuthenticated ? <Outlet /> : <Navigate to="/login" />;
};
export default ProtectedRoute;
user
정보는 위와 같이 가져오면 된다.
이제 전체적인 로직이 매우 간단해졌다. 👍
나머지 로직은 너무 길어서 첨부하지 못했다.
[소스코드]에서 확인하는것이 더 좋을것 같아서 첨부한다.
이렇게 이번 심화주차 과제로 마무리가 됐다.
도전과제인 Tanstack Query
를 적용시키지 못한것은 다음 팀 과제에서 꼭 사용해보자.
오늘 챌린지반에서 Tanstack Query
를 실습했기 때문에,
다음에는 잘 할 수 있을것 같다.
그 때는 진짜로 잘해보자. 화이팅!!!