nextjs
zustand 상태관리에서 localStorage 사용하는 방법

zustand 상태관리에서 localStorage 사용하는 방법

상태 변수 값을 localStorage에 저장하는 방법

zustandStore.tsx
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
 
interface SettingConfigState {
    useMic: boolean;
    useCamera: boolean;
    toggleMic: () => void;
    toggleCamera: () => void;
    hasHydrated: boolean;
    setHasHydrated: (state: boolean) => void;
}
 
const storeKey = "setting-config-store";
 
export const useSettingConfigStore = create<SettingConfigState>()(
    persist((set) => ({
        useMic: true,
        useCamera: true,
        hasHydrated: false,
        toggleMic: () => set((state) => ({ useMic: !state.useMic })),
        toggleCamera: () => set((state) => ({ useCamera: !state.useCamera })),
        setHasHydrated: (state: boolean) => set({ _hasHydrated: state }),
    }), {
        name: storeKey,
        onRehydrateStorage: () => (state) => {
            state?.setHasHydrated(true);
        },
    })
)
 
// window의 'storage' 이벤트를 감지하는 리스너
// A, B, C 세개의 탭이 있을 때 A탭에서 변경한 값이 B탭 에도 반영되게 하기 위한 코드
// 만약 A에서 값을 바꿨을 때 persist() 로 인하여 localStorage에 저장될거고, 이러면 storage 이벤트가 발생할 텐대 이걸 감지해서 값 갱신
if (typeof window !== 'undefined') {
    window.addEventListener('storage', (event) => {
        // 변경된 localStorage의 키가 우리 스토어의 키와 같은지 확인
        if (event.key === storeKey) {
            // 다른 탭에서 변경된 새로운 상태를 파싱
            const newState = JSON.parse(event.newValue || '{}');
 
            // 현재 탭의 스토어 상태를 새로운 상태로 강제 업데이트
            useSettingConfigStore.setState(newState.state);
        }
    });
}

onRehydrateStorage ?

아직 영속화된 데이터(localStorage) 못 불러왔는데 사전에 렌더링 되버려서 초기 데이터로 렌더링 되었다가, 불러온 이후 값으로 다시 렌더링되어 사용자가 보는 페이지에 변화가 생기는 문제가 발생할 수 있다

그렇기 때문에, 영속화된 데이터를 모두 불러오고 나서 최초 렌더링 되도록 할 필요가 있는데 이때 영속화된 데이터 불러오기가 완료되었다 라는 시점을 잡을 수 있게 하는 기능이다

  • hasHydrated : 영속화된 데이터 불러오기가 완료되었는지 확인하는 플래그
  • onRehydrateStorage : 영속화된 데이터를 불러오기가 완료된 후 발생하는 이벤트 핸들러
store쓰는쪽.tsx
const useMic = useSettingConfigStore((state) => state.useMic);
const useCamera = useSettingConfigStore((state) => state.useCamera);
 
const { toggleMic, toggleCamera, hasHydrated } = useSettingConfigStore();
 
// 아직 영속화된 데이터 불러오기가 완료되지 않았을 경우 렌더링 작업은 진행하지 않고 바로 return
if (!hasHydrated) {
    return 
}
 
// 렌더링을 위한 코드 진행
return <>
 /*
 ~~~
 */
</>