[심화과제] MBTI 테스트 만들기3

안현희·2024년 11월 27일
0

React를 배워보자!

목록 보기
24/30

0. Context API

를 시작하기전에 또 놓친부분이 있었다.

  • 원래 이런 사람이 아닌데 이번 프로젝트때는 왜 이렇게 놓친게 많은것인지...

  • 안그래도 공통 컴포넌트인 Header를 만들고 사용해야하는 페이지마다 계속해서 넣어주는것이 너무 불편했는데 이미 이런 가이드가 존재하고 있었다...
    매우 반성한다.

  • 반성은 반성이고 할건하자!


1. 레이아웃

  • 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에서 감싸주면 끝!!!

간단하게 마무리 했다.


2. 트러블슈팅

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

//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

//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

//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를 실습했기 때문에,
    다음에는 잘 할 수 있을것 같다.
    그 때는 진짜로 잘해보자. 화이팅!!!


그럼이만

profile
모든 순간에 최선을 다합니다.

0개의 댓글

관련 채용 정보