본문 바로가기
STUDY/Project

전역 상태 관리하기 - AuthContext, 로그아웃 기능

by Y.Choi 2025. 5. 5.
728x90
반응형

 

context는 React에서 전역 상태 관리를 가능하게 해주는 도구이다.

props를 컴포넌트 계층을 따라 계속 전달하지 않고 공통적으로 필요한 데이터를 한 곳에서 관리하고 사용할 수 있다.

 

지금까지는 로그인 성공 후 navigate()만 했고 사용자 정보를 다른 컴포넌트에서 사용하지 않았기 때문에 필요하지 않았지만 여러 곳에서 로그인한 사용자의 정보를 유지 관리하려면 필요하다.

 

React Context는

- 로그인한 사용자 정보 ( user )

- 로그인 상태 여부 ( isAuthenticated )

- token

- 로그아웃 함수 등

 

앱 전역에서 쉽게 접근하고 사용할 수 있도록 만든다.

 

 

| context 디렉터리 구조

frontend/
└── src/
    └── context/
        └── AuthContext.js

 

 

| Context 작성 (로그아웃 포함)

frontend/src/context/AuthContext.js

import { createContext, useState, useEffect } from 'react';

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(() => {
    const storedUser = localStorage.getItem('user');
    return storedUser ? JSON.parse(storedUser) : null;
  });

  useEffect(() => {
    // 로그인 상태 유지 위해 localStorage에서 유저 정보 불러오기
    const storedUser = localStorage.getItem('user');
    if (storedUser) {
      try {
        setUser(JSON.parse(storedUser));
      } catch (e) {
        console.error('유저 정보 파싱 오류', e);
      }
    }
  }, []);

  const login = (userData) => {
    localStorage.setItem('user', JSON.stringify(userData));
    setUser(userData);
  };

  const logout = () => {
    localStorage.removeItem('user');
    localStorage.removeItem('token');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

 

user 정보를 전역으로 관리하며 로그인/로그아웃 상태를 유지하고, 모든 페이지에서 useContext(AuthContext)로 접근할 수 있게 한다.

 

 

| 전역 등록하기

frontend/src/App.js

import { AuthProvider } from './context/AuthContext';

function App() {
  return (
    <AuthProvider>
      <Router>
        {/* 라우팅 컴포넌트 */}
      </Router>
    </AuthProvider>
  );
}

 

기존의 App.js에 라우터를 <AuthProvider>로 감싸준다.

 

 

이제, 로그인 시 AuthContext의 login()함수를 호출해서 사용자 정보를 전역 상태로 반영하도록 LoginPage.js를 수정해야한다.

 

 

| LoginPage.js에 AuthContext 적용

frontend/src/pages/LoginPage.js

// src/pages/LoginPage.js
import React, { useState, useContext } from 'react';
import axios from 'axios';
import { AuthContext } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';

const LoginPage = () => {
  const [formData, setFormData] = useState({ email: '', password: '' });
  const [message, setMessage] = useState('');
  const { login } = useContext(AuthContext); // context에서 login 함수 가져오기
  const navigate = useNavigate();

  const handleChange = (e) => {
    setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const res = await axios.post('http://localhost:5000/api/auth/login', formData);
      const { token, ...user } = res.data;

      // context 상태와 localStorage 동시 반영
      login(user);
      localStorage.setItem('token', token);

      setMessage('로그인 성공 🎉');
      navigate('/dashboard');
    } catch (err) {
      console.error(err);
      setMessage(err.response?.data?.message || '로그인 실패 😢');
    }
  };

  return (
    <div>
      <h2>로그인</h2>
      <form onSubmit={handleSubmit}>
        <input type="email" name="email" onChange={handleChange} placeholder="이메일" required />
        <input type="password" name="password" onChange={handleChange} placeholder="비밀번호" required />
        <button type="submit">로그인</button>
      </form>
      <p>{message}</p>
    </div>
  );
};

export default LoginPage;

 

useContext를 통해 login 함수를 가져와 로그인 성공시 호출한다.

이전에는 localStorage에 직접 저장 후 대시보드에서 다시 파싱했지만 이제 context가 저장해 주기 때문에 중복이 사라졌다.

 

 

| DashboardPage.js 수정, 로그아웃 버튼 추가

이전에 작업한 DashboardPage.js가 동작은 하지만 이제 Authcontext를 도입했기 때문에 더 깔끔하고 React스러운 방식으로 리팩터링 하는 것이 좋다. 

localStorage에 직접 접근하지 않고 AuthContext를 통해 전역 상태로 사용자 정보를 관리하는 것이 유지보수, 테스트, 확장성 측면에서 훨씬 좋기 때문이다.

 

frontend/src/pages/DashboardPage.js

import React, { useContext } from 'react';
import { AuthContext } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';

const DashboardPage = () => {
  const { user, logout } = useContext(AuthContext);
  const navigate = useNavigate();

  if (!user) {
    return <p>로그인이 필요한 페이지입니다. 🔒</p>;
  }

  const handleLogout = () => {
    logout(); // context의 logout
    navigate('/login');
  };

  return (
    <div>
      <h2>대시보드 🧭</h2>
      <p>환영합니다, {user.username}님!</p>
      <p>이메일: {user.email}</p>
      <button onClick={handleLogout}>로그아웃</button>
    </div>
  );
};

export default DashboardPage;

 

로그아웃 버튼을 클릭하면 AuthContext의 user 값이 null로 바뀌고 localStorage의 user, token정보도 삭제된다.

그리고 바로 로그인 페이지로 이동한다.

context를 적용해 더이상 lacalStorage에 직접 파싱하지 않고 AuthContext의 user와 logout을 통해 전역상태를 관리한다.

 

| Protextedroute.js 수정

// src/components/ProtectedRoute.js
import React, { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import { AuthContext } from '../context/AuthContext';

const ProtectedRoute = ({ children }) => {
  const { user } = useContext(AuthContext);
  return user ? children : <Navigate to="/login" />;
};

export default ProtectedRoute;

 

토큰 유무가 아닌 context의 user 상태로 보호 판단한다.

 

 

이제 로그인이 정상 작동하는지 확인한다.

 

 

 

로그아웃 버튼도 눌러본다.

 

 

 

로그아웃 버튼을 누르니 로그인 화면으로 넘어가고 key와 value에 token과 user정보가 삭제된 것을 알 수 있다.

 

이로써,

회원가입, 로그인, 로그아웃을 모두 구현했다.

728x90
반응형