한 프로젝트에서 회원 관리 담당인 내가 로그아웃 로직을 구현했는데, 팀원이 여기에 상태 관리 코드를 추가했다.
const useUserStore = create<UserState>()(
persist(
(set, get): UserState =>
({
user: null,
role: null,
isAuthenticated: false,
isLogin: false,
accessToken: null,
setAccessToken: (token) => set({ accessToken: token }),
setUser: (user) => set({ user }),
// 생략
logout: async () => {
const { user } = get();
const healthStore = useHealthStore();
const galleryStore = useGalleryStore();
if (!user) {
console.error('로그인 해주세요.');
return;
}
try {
await getLogout(user.id);
set({
isLogin: false,
isAuthenticated: false,
user: null,
accessToken: null,
});
healthStore.reset(); // 로그아웃시 건강 정보 store reset
galleryStore.reset(); // 로그아웃시 갤러리 store reset
} catch (error) {
console.error(error);
}
},
// 이하생략
Zustand를 사용해 userStore를 관리하는 과정에서, logout 함수 내에서 useHealthStore와 useGalleryStore를 호출하여 각각의 상태를 초기화하려고 했던 것 같다. 그러나 아래와 같은 오류가 발생했다!
" 리액트 훅은 함수 컴포넌트 내부에서만 호출될 수 있습니다. 이는 훅들이 리액트의 내부 상태와 라이프사이클에 접근하므로, 함수 컴포넌트 밖에서 호출하게 되면 예상치 못한 동작을 유발할 수 있기 때문입니다. "
-> logout 함수는 컴포넌트가 아닌 Zustand의 Store 내부에서 선언되었기 때문에 useStore과 같은 React Hook을 직접 사용할 수 없던 것이었다!
그럼 로그아웃을 하면서 두 store를 어떻게 reset해야될까...
✅ getState를 활용한 직접 접근
logout: async () => {
const { user } = get();
const healthStore = useHealthStore.getState();
const galleryStore = useGalleryStore.getState();
if (!user) {
console.error('로그인 해주세요.');
return;
}
try {
await getLogout(user.id);
set({
isLogin: false,
isAuthenticated: false,
user: null,
accessToken: null,
});
healthStore.reset(); // 건강 정보 store reset
galleryStore.reset(); // 갤러리 store reset
} catch (error) {
console.error(error);
}
}
-> 이렇게 Zustand Store의 getState()를 사용하면 훅을 호출하지 않고도 Store의 상태와 메서드에 접근할 수 있다!
❓ getState
- Zustand 같은 상태 관리 라이브러리에서 현재 Store의 상태를 직접 가져올 때 사용
- 👍장점
- React의 Hook은 컴포넌트 내부에서만 호출 가능하지만, getState()는 어디서든 호출할 수 있어 유연함.
- API 호출, 이벤트 핸들러, Zustand store 내부 등에서 상태를 가져와야 할 때 훅 없이 직접 접근 가능.
- useStore 같은 훅을 사용하면 값이 변경될 때마다 리렌더링될 수 있지만, getState()는 상태를 직접 가져오기 때문에 불필요한 리렌더링을 방지할 수 있음.
- 한 Store에서 다른 Store의 상태를 변경할 때 getState()를 사용하면 쉽게 접근 가능.
- 👎단점
- useStore 훅과 달리 getState()는 상태 변화에 반응하지 않음. 즉, getState()로 가져온 값이 변경되어도 자동으로 UI가 업데이트되지 않음.
- useStore 훅을 사용하면 React DevTools에서 상태 변화를 추적하기 쉽지만, getState()를 남용하면 예상치 못한 상태 변화가 발생할 수 있음.
- getState()를 호출하는 시점의 상태를 가져오기 때문에, 예상과 다르게 이전 상태를 참조할 가능성이 있음.
✅ logout 페이지를 별도로 만들어 이동
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import useUserStore from '../stores/UserStore';
import useHealthStore from '../stores/HealthStore';
import useGalleryStore from '../stores/GalleryStore';
function LogoutPage() {
const { isLogin, logout } = useUserStore();
const ResetHealth = useHealthStore().reset
const ResetGallery = useGalleryStore().reset
useEffect(() => {
logout()
ResetHealth()
ResetGallery()
}, []);
function render() {
return <Navigate to="/" replace={true} />
}
return isLogin ? null : render()
}
export default LogoutPage;
팀원분은 이렇게 로그아웃 페이지를 따로 구현해서 로그아웃 버튼이 있는 곳에서 리디렉션을 시키는 방법을 제안했다.
이렇게 구현한다면 React의 Hook 규칙을 어기지 않으면서, 컴포넌트 단위 관리로 의존성을 줄일 수 있다.
하지만 추가적인 페이지 이동을 해야하고, 여러 곳에서 Store 초기화가 필요할 경우 코드가 중복될 가능성이 있다.
store 초기화가 오래 걸려 UX를 저하시키거나, 다른 곳에서 Store 초기화를 하지 않았기에 위 코드를 유지했다.
✅ 기존 페이지에서 로그아웃 실행
근데 생각해보면 로그아웃 버튼이 있는 페이지에서 reset을 할 수도 있는 거 아닌가?
async function handleLogout() {
const userStore = useUserStore();
const healthStore = useHealthStore();
const galleryStore = useGalleryStore();
await userStore.logout();
healthStore.reset();
galleryStore.reset();
};
React 함수 컴포넌트 내부에서 사용하니까 규칙을 위반하지 않아 getState를 사용하지 않아도 된다.
하지만 getState()를 쓰면 어디서든 사용 가능하니 더 권장되는 방식인 것 같기도 하다...
그리고 리셋할 저 두 store들은 상태 변화를 추적할 일이 없으니까 굳이 useStore를 사용할 필요가 없긴 하다.
async function handleLogout() {
const userStore = useUserStore.getState();
const healthStore = useHealthStore.getState();
const galleryStore = useGalleryStore.getState();
await userStore.logout();
healthStore.reset();
galleryStore.reset();
};
결론은 나라면 이렇게 로그아웃을 하는 페이지에서 getState를 사용해 reset해주는 것을 채택했을 것 같다!
'트러블 슈팅' 카테고리의 다른 글
[Axios] 서버에서 사용자가 업로드한 파일을 받아오지 못하는 에러 핸들링 (0) | 2025.05.03 |
---|---|
[Next.js + Zustand] localStorage hydration 에러 핸들링 (0) | 2025.05.02 |
[Node.js] prisma 클라이언트 초기화 오류 해결하기 (1) | 2025.04.16 |
[Next.js] Image 경고(LCP, 종횡비) 해결하기 (0) | 2025.04.10 |