React 상태 관리 가이드( JUSTAND )

개요

상태관리 라이브러리는 왜 필요한가?

React의 기본 한계

  • Prop Drilling: 깊은 컴포넌트에 데이터 전달 시 중간 컴포넌트들을 모두 거쳐야 함
  • 전역 상태 관리 어려움: 여러 컴포넌트가 공유하는 상태 관리가 복잡
  • 상태 변화 추적 곤란: useState로 여기저기서 setState 호출 시 어디서 상태가 변경되었는지 추적 어려움(집중화해서 관리하면 해당 위치에서만 상태가 업데이트 - 집중화, 목적을 명확하게 사용 )

위와 같은 문제로 나는 주로 justand + React-query를 사용한다. react-query로 서버에서 오는 데이터를 관리해서 SSR에서도 데이터를 관리하고 Justand로 클라이언트 상태를 관리한다. justand는 Redux-devtools을 지원해서 상태 변화를 시간순으로 추적하여 디버깅 편리하다.

상태 분류 및 해결 방법

1. 전역 상태Zustand

const useUserStore = create((set) => ({
  user: null,
  login: (user) => set({ user, isLoggedIn: true }),
  logout: (user) => set({ user, isLoggedIn: false }),
}));
  • 적용 대상: 사용자 정보, 장바구니, 테마, 로그인 상태 등
  • 특징: 여러 페이지에서 공유, 새로고침해도 유지 필요
// 유지를 하기위해서는 persist 옵션을 사용해야한다.
const useStore = create(
  persist(
    // 첫 번째 인자: 일반적인 Zustand store 함수
    (set, get) => ({
      user: null,
      cart: [],
      
      login: (userData) => set({ user: userData }),
      addToCart: (item) => set((state) => ({ 
        cart: [...state.cart, item] 
      })),
    }),
    
    // 두 번째 인자: persist 설정 객체 / localsotorage의 app-storage로 저장됨
    {
      name: 'app-storage',        // localStorage 키 이름
      partialize: (state) => ({}), // 어떤 상태를 저장할지
      storage: createJSONStorage(() => localStorage), // 저장소 타입
    }
  )
);

2. 페이지별 복잡한 상태Context API

전역으로 쓸 요소는 아닌데 현재 페이지에서 사용해야하는 전역변수가 있다. 예를들면 복잡한 Canvas화면에서 동작을 할 때 전체 요소를 중앙에서 관리해야하는 경우가 있다. 해당 케이스를 위해서는 Context API를 사용해서 해당 페이지만의 상태를 관리하는게 좋다.

Atomic Design Pattern에서 복잡한 Organisms이나 templates에서도 사용된다.

const CheckoutPageContext = createContext();

function CheckoutPageProvider({ children }) {
  const [currentStep, setCurrentStep] = useState(1);
  const [formData, setFormData] = useState({});
  
  return (
    <CheckoutPageContext.Provider value={{ currentStep, formData, ... }}>
      {children}
    </CheckoutPageContext.Provider>
  );
}
  • 적용 대상: 특정 페이지의 폼 데이터, 단계 상태, 모달 상태 등
  • 특징: 해당 페이지에서만 사용, props drilling 방지

SSR을 사용하는 환경(Next.js)에서의 주의사항

서버에서 받아오는 중요데이터는

// ✅ 서버 상태 - TanStack Query/SWR
const { data: user } = useQuery('/api/user');

// ✅ 클라이언트 상태 - Zustand (SSR 안전 패턴)
const useClientStore = create((set) => ({
  theme: null,  // 초기값 null
  isHydrated: false,
  
  hydrate: () => {
    const savedTheme = localStorage.getItem('theme') || 'light';
    set({ theme: savedTheme, isHydrated: true });
  },
}));

// 컴포넌트에서 안전하게 사용
function Header() {
  const { theme, isHydrated } = useClientStore();
  const currentTheme = isHydrated ? theme : 'light'; // 기본값 제공
  
  return <header className={currentTheme}>...</header>;
}

🎯 최종 권장 패턴

상태 타입도구사용 사례
전역 상태Zustanduser, cart, theme, authToken
서버 상태TanStack QueryAPI 데이터, 캐싱
페이지별 복잡한 상태Context API폼, 단계, props drilling 해결
간단한 로컬 상태useState모달, 입력값, 토글

핵심: 상태의 범위와 복잡도에 따라 적절한 도구를 선택하고, Next.js에서는 서버/클라이언트 상태를 명확히 구분하여 사용하자!