최근 진행중인 프로젝트에서 사용자 데이터를 로드하는 과정에 문제를 겪었다.
로그인을 완료한 회원이 메인 홈 화면에 처음 진입할 때, 해당 회원의 데이터를 바로 화면에 렌더링해주는 것이 나의 의도였으나,
실제 동작에서는 데이터가 정상적으로 불러와지지 않는 문제가 발생했다.
원인은 Firebase 인증 흐름과 비동기 처리 방식의 타이밍 문제였다.
내가 의도했던 동작은 아래와 같았다.
하지만
auth.currentUser는 현재 Firebase 인증 상태에서 보관하고 있는 사용자의 정보를 반환하는데, Firebase 인증이 완료되지 않은 상태에서 호출하면 null을 반환할 수도 있다.
즉, 로그인한 유저라 하더라도, 처음 앱이 로드될 때 auth.currentUser가 즉시 값을 제공하지 못할 수 있다.
이로 인해, 파이어스토어에서 데이터를 가져올 때 null을 기반으로 요청하여 실패하는 문제가 발생했다.
이전 코드 예시 (잘못된 방식)
import { auth } from "../firebaseConfig";
const user = auth.currentUser;
console.log(user);
onAuthStateChanged란?
onAuthStateChanged는 Firebase에서 제공하는 메서드로, 사용자의 로그인 상태가 변경될 때마다 콜백 함수를 실행한다. 즉, 이 메서드를 사용하면 Firebase가 사용자 정보를 가져올 때까지 기다렸다가, 이후 상태를 업데이트할 수 있다.
참고로 onAuthStateChanged는 한 번 실행하고 끝나는 함수가 아니라, Firebase에서 인증 상태를 계속 감지하는 리스너 역할을 한다.
즉, 앱이 실행되는 동안 Firebase 인증 상태를 계속 "구독"하는 방식이기 때문에, 인증 상태가 변경될 때마다 실행되어 메모리 누수를 방지하는 처리가 추가로 필요하다.
AuthContext 생성
처음에는 onAuth~ 메서드를 홈메인에서만 간단하게 적용하려고 했는데, 생각해보니 전역적으로 관리하는 것이 훨씬 효율적일 것 같아서
React의 Context API를 활용하여 AuthContext를 만들었다.
📌 AuthContext.js (로그인 상태 관리 컨텍스트)
import React, { createContext, useEffect, useState } from "react";
import { auth } from "../firebaseConfig";
import { onAuthStateChanged } from "firebase/auth";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
setLoading(false);
});
return () => unsubscribe(); // ✅ 메모리 누수 방지를 위해 구독 해제
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
};
코드 설명
우선 useState를 사용하여 user와 loading 상태를 관리한다.
useEffect에서 onAuthStateChanged(auth, callback)을 사용하여 로그인 상태를 감지하고, 사용자가 로그인하거나 로그아웃할 때 setUser(currentUser)로 상태를 업데이트한다.
추가로, return () => unsubscribe();를 추가하여 컴포넌트가 언마운트될 때 구독을 해제하여 메모리 누수를 방지한다.
최종적으로 AuthContext.Provider를 사용하여 로그인 상태를 전역에서 사용할 수 있도록 한다.
이제 AuthProvider를 App.js에서 최상위 컴포넌트로 감싸 로그인 상태를 전역에서 관리할 수 있도록 설정한다.
📌 App.js (AuthProvider 적용)
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./contexts/AuthContext";
import HomePage from "./pages/HomePage";
import LoginPage from "./pages/LoginPage";
const App = () => {
return (
<AuthProvider>
<Router>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
</Routes>
</Router>
</AuthProvider>
);
};
export default App;
이제 AuthProvider가 전체 애플리케이션을 감싸게 되어, 모든 컴포넌트에서 AuthContext를 사용할 수 있게된다.
각 페이지에서 AuthContext를 사용하여 현재 로그인한 사용자 정보를 가져올 수 있게된다.
📌 HomePage.js 예시 (로그인한 사용자 정보 확인)
import React, { useContext } from "react";
import { AuthContext } from "../contexts/AuthContext";
const HomePage = () => {
const { user, loading } = useContext(AuthContext);
if (loading) {
return <p>로딩 중...</p>;
}
return (
<div>
{user ? (
<h1>환영합니다, {user.email}님!</h1>
) : (
<h1>로그인해주세요.</h1>
)}
</div>
);
};
export default HomePage;
코드 설명
useContext(AuthContext)를 사용하여 user와 loading을 가져온다.
loading이 true이면 "로딩 중..." 메시지를 표시하여, 사용자 정보가 아직 불러와지지 않았음을 알린다.
user가 존재하면 사용자 이메일을 표시하고, 없으면 "로그인해주세요."를 표시한다.