이전에 구현했던 좋아요 기능에서 한가지 문제가 있었다.
firebase db에서 사용자가 좋아요 표시한 목록을 가져와 버튼 눌림 여부가 잘 표시되다가, 해당 페이지에서 새로고침을 하면 버튼 상태가 모두 초기화됐다. (db에는 정보가 잘 남아있는데도 불구하고)
가장 먼저 일차원적인 해결책을 생각해보았으나, 다음과 같은 문제가 있었다.
- 로컬에 상태값을 저장해두기?
- 로그아웃 한 경우, 이 값이 남아있을 수 있다.
- 해당 페이지 새로고침 방지?
- 당장으로써는 좋을 수 있으나, 서비스 사용성을 생각한다면 이 기능은 회원가입 또는 정보 입력 등 호흡이 긴 페이지에서 유저의 노력(?)이 사라지지 않도록 방지하기 위함 등 보수적으로 사용하는 편이 좋다.
기본적으로 클라이언트에서 관리하는 전역 상태 관리는 새로고침 했을 때 초기화된다.
다시 말해, 로그인 정보나 인증 상태 등을 클라이언트 애플리케이션 내부에서 관리한다는 뜻이다. 현재 사용하고 있는 React와 같은 프레임워크에서는 이러한 상태를 전역 상태로 저장하여 기억한다. 그러나 새로고침을 한다면 클라이언트가 새로 렌더링되기 때문에 메모리 안의 상태가 모두 사라진다.
이 문제를 해결하기 위해 세션 저장소 또는 쿠키 등 영구적인 저장소를 이용해 유지해야 한다.
- 세션(Session): 브라우저 탭 또는 윈도우가 열려있는 동안에만 데이터를 유지
- 쿠키(Cookie): 서버 또는 클라이언트 측에서 일정 기간을 설정하여 유지.
우선 기존 로그인 로직을 확인해보니, 세션 스토리지를 잘 형성해두었다.
useEffect(() => {
if (currentlyLoggedIn) {
navigate("/profile"); // 이미 로그인된 상태라면 프로필 페이지로
}
}, [currentlyLoggedIn, navigate]);
const handleSignIn = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
await setPersistence(auth, browserSessionPersistence); // 세션에 유저 정보 저장
const userCredential = await signInWithEmailAndPassword(
auth,
signInEmail,
signInPw
);
console.log(userCredential);
const user = userCredential.user;
if (rememberMe) {
// 체크된 경우
localStorage.setItem("username", signInEmail);
} else {
localStorage.removeItem("username");
}
setCurrentlyLoggedIn(true);
setIsLoggedIn(true);
navigate("/"); // 성공 시 홈으로 이동
} catch (error) {
기존 로그아웃 버튼은 아래와 같다.
import React from "react";
import { auth } from "../firebase/firebaseConfig";
import { useNavigate } from "react-router-dom";
import { useContext } from "react";
import { AuthContext } from "../context/AuthContext";
import s from "../stores/styling";
import useModal from "../hooks/ModalHook";
import Modal from "../components/Modal";
interface LogoutButtonProps {
className?: string;
}
const LogoutButton: React.FC<LogoutButtonProps> = ({
className,
}) => {
const { setCurrentlyLoggedIn } = useContext(AuthContext);
const { isModalOpen, handleOpenModal, handleCloseModal } = useModal();
const navigate = useNavigate();
const handleLogout = () => {
setCurrentlyLoggedIn(false); // 로그아웃 처리
handleCloseModal(); // 모달 닫기
navigate("/"); // 로그아웃 후 홈으로 이동
};
return (
<>
<Modal
isOpen={isModalOpen}
onClose={handleCloseModal}
modalTitle={"잠깐!"}
showCheckbox={false}
text={
<s.StyledP className="modal-text">
정말 로그아웃 하시겠습니까?
</s.StyledP>
}
modalButtonClose={"취소"}
addButton={true}
modalButtonOption={"로그아웃"}
onOptionClick={handleLogout}
/>
<s.OutIcon onClick={handleOpenModal} />
</>
);
};
export default LogoutButton;
세션이 제대로 형성되었음에도 왜 이같은 오류가 나타나는 것일까?
문제는 await auth.signOut()으로 명확한 로그아웃을 설정해주지 않았다는 것이다.
로그아웃을 확실하게 설정하지 않은 경우, 다음과 같은 문제가 발생할 수 있다.
- 세션에 상태가 제대로 저장되지 않아 로그인 정보가 손실될 수 있다.
- 새로고침, 탭 다시 열기 등 동작을 수행했을 때 세션 정보가 불완전하게 남아있거나, 비정상적으로 초기화 될 수 있다.
따라서 아래와 같이 코드를 일부 수정했다.
const handleLogout = async () => {
try {
await auth.signOut(); // Firebase 로그아웃 처리
setCurrentlyLoggedIn(false); // 로그아웃 후 로컬 상태 업데이트
navigate("/"); // 로그아웃 후 홈으로 이동
handleCloseModal(); // 모달 닫기
} catch (error) {
console.error("로그아웃 중 오류 발생:", error);
}
};
위와 같이 로그아웃 로직을 정리해주니, 위시리스트 목록에 있는 요소의 버튼 상태가 페이지 새로고침 이후에도 정상적으로 표시됐다.
비슷한 맥락으로, 유저 프로필 페이지에서도 로그인을 했는데도 새로고침을 하는 순간 모든 userdata가 받아지지 않고 초기화되는 현상이 있었다.
아래는 기존 프로필 페이지에서 유저 정보를 가져오는 방법이다.
useEffect(() => {
const fetchUserData = async () => {
if (auth.currentUser) {
const userDocRef = doc(db, "users", auth.currentUser.uid);
const userDoc = await getDoc(userDocRef);
if (userDoc.exists()) {
const data = userDoc.data();
setUserData(data);
setPhotoURL(data.photoURL);
} else {
console.log("No such document!");
}
}
};
fetchUserData();
}, []);
위 코드의 문제점은, 유저의 로그인과 로그아웃 상태를 auth.currentUser를 직접 사용하여 확인하는데, 컴포넌트 마운트 시 한 번만 실행된다는 점이다.
auth.currentUser가 존재할 때 데이터를 가져와야 하지만 auth.currentUser의 값이 즉시 업데이트 되지 않을 수 있다. 따라서 사용자 인증 상태가 변경된 경우 바로 반영되지 않을 가능성이 있다.
만일 사용자의 로그인 상태가 바뀌면 이 변화를 감지하지 못하고, 새로고침을 해야 데이터를 다시 불러올 수 있다.
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
// 유저가 로그인되어 있을 때만 데이터를 가져옴
fetchUserData(user.uid);
} else {
setUserData(null); // 유저가 없다면 null로 설정
}
});
return () => unsubscribe(); // 컴포넌트 언마운트 시 구독 해제
}, []);
수정된 코드에는 onAuthStateChanged()를 사용하여 인증 상태의 변화를 실시간으로 자동 감지하도록 보완했다.
이로써 사용자가 로그인, 또는 로그아웃 할 때마다 이벤트가 발생하기 때문에 별도의 새로고침을 할 필요가 없다.
'React.ts' 카테고리의 다른 글
리액트 가상 키보드 만들기 - 오디오 재생하기, Audio 객체 (0) | 2024.10.25 |
---|---|
리액트 가상 키보드 만들기 - 레이아웃 및 디자인, 배열과 map을 활용한 키 매핑 (3) | 2024.10.25 |
GSAP 활용하여 ScrollEvent 쉽게 구현하기 - 2 (0) | 2024.08.12 |
GSAP 활용하여 ScrollEvent 쉽게 구현하기 - 1 (0) | 2024.08.07 |
React TypeScript - OpenAI DALL.E API로 이미지 생성하기 (0) | 2024.07.17 |