Zustand를 사용한 긴급 공지 모달 관리: React 상태관리를 더 깔끔하게

Ryomi·2025년 7월 10일
2
post-thumbnail

이전 글 zustand 간단 사용법!

최근 긴급 공지 모달 기능을 리팩토링하면서 Zustand를 도입하게 되었습니다.
이 글은 기존 방식의 문제점과, 해당 문제점을 Zustand로 개선한 경험을 코드 중심으로 정리한 글입니다.


🧩 기존 구현 방식: 점점 복잡해졌던 컴포넌트 로직

우선 긴급 공지 모달 기능은 다음 과정을 거쳐 렌더링되고 있었습니다.

  • 공지 리스트를 서버에서 받아옴
  • 각 공지의 상세 내용을 모달로 보여줌
  • 하루 동안 보지 않기, 닫기 등의 조건 처리

❌ Before: 컴포넌트에 모든 로직이 몰려 있었다.

// EmergencyNtcModal.tsx
useEffect(() => {
  const isTodayHidden = localStorage.getItem(`emergency_${id}`) === getToday();
  if (!isTodayHidden) {
    fetchNoticeDetail(id).then((data) => setModalData(data));
    setShow(true);
  }
}, []);
  • 코드를 보시면 각 모달이 나타날 때마다, useEffect, localStorage, api 호출 등 동일한 로직이 반복하게 됩니다.
  • 공지 리스트 상태는 부모에서 관리하지만, 상세 내용은 자식에서 개별 호출 → 이렇게되면 비동기 처리가 복잡해지겠죠?
  • 하루 숨김, 열림 상태 등 UI 상태가 흩어짐으로 → 유지보수가 어려웠습니다.

📚 Zustand로 리팩토링: 상태, 로직을 store로 분리

🔧 Zustand store 구성

// store/useEmergencyNoticeStore.ts
interface EmergencyState {
  emerNtcIds: number[];
  modalStates: { [id: number]: boolean };
  noticeData: { [id: number]: Data };
  fetchNoticeList: () => Promise<void>;
  fetchNoticeDetail: (id: number) => Promise<void>;
  checkAndHideModal: (id: number) => boolean;
  hideModalForDay: (id: number) => void;
}

상태와 행동(Action)을 하나의 store에서 모두 관리하게 만들었습니다.


✔️ 공지 리스트 가져오기

// store 내부
fetchNoticeList: async () => {
  const list = await fetchNoticeListFromServer();
  const visibleIds = list
    .filter((item) => !isHiddenToday(item.id)) // localStorage 체크
    .map((item) => item.id);

  set({ emerNtcIds: visibleIds });
},

✔️ 하루 숨기기 체크 & 숨기기 처리

checkAndHideModal: (id) => {
  const today = getToday();
  return localStorage.getItem(`emergency_${id}`) === today;
},

hideModalForDay: (id) => {
  const today = getToday();
  localStorage.setItem(`emergency_${id}`, today);
},

📦 컴포넌트는 이렇게 깔끔해졌다

// DashBoard.tsx
const { emerNtcIds } = useEmergencyNoticeStore();

return (
  <>
    {emerNtcIds.map((id, index) => (
      <EmergencyNtcModal key={id} ntcId={id} index={index} />
    ))}
  </>
);
// EmergencyNtcModal.tsx
const { modalStates, noticeData, fetchNoticeDetail, hideModalForDay } = useEmergencyNoticeStore();

useEffect(() => {
  fetchNoticeDetail(ntcId);
}, [ntcId]);

✅ After: store에서 모든 로직 관리

// store 내부
checkAndHideModal: (id) => localStorage.getItem(`emergency_${id}`) === getToday()

// 컴포넌트
useEffect(() => {
  fetchNoticeDetail(id);
}, []);

✨ 얻은 인사이트

  • 공통 로직을 store로 분리함으로써 컴포넌트의 책임이 명확해졌습니다.
  • localStorage, 비동기 로직, 상태 판단이 중앙 집중화되어 테스트 및 재사용성 향상되었습니다.
  • 여러 개의 모달이 떠야 하는 상황에서도 modalStates로 상태를 명확하게 관리할 수 있게 되었습니다.
  • Zustand는 Context보다 간단하고, Redux보다 가벼운 것 같습니다.
    작은 기능 단위 전역 상태 관리에 최적!

📌 마무리

이번 리팩토링을 통해 Zustand가 얼마나 직관적이고 유연한 도구인지 체감한 것 같습니다.
컴포넌트에 몰려 있던 비즈니스 로직을 store로 분리하면서 코드 유지보수성과 확장성 모두 개선된 것 같습니다.


📁 참고

  • Zustand 공식 문서
  • 상태 구조에 따라 zustand-persist로 localStorage도 store 내부에서 자동 관리 가능
    → 다음 개선 포인트로 고려 중

피드백은 언제든지 환영합니다!

profile
making a list, checking it twice 🐥

0개의 댓글