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정보가 삭제된 것을 알 수 있다.
이로써,
회원가입, 로그인, 로그아웃을 모두 구현했다.
'STUDY > Project' 카테고리의 다른 글
프론트엔드 기능별 라우터 분리 - routes/ (0) | 2025.05.06 |
---|---|
게시판 페이지 작성 - PostListPage, PostDetailPage, CreatePostPage (0) | 2025.05.06 |
로그인 응답 구조로 인해 발생한 오류 "undefined" is not valid JSON (0) | 2025.05.04 |
인증이 필요한 보호 페이지 - ProtectedRoute, DashboardPage (0) | 2025.05.04 |
로그인 기능 구현 - LoginPage (0) | 2025.05.04 |