내일배움캠프

230809 React Zustand Modal 전역 상태 관리하기

Neda 2023. 8. 9. 20:29

230809 React Zustand Modal 전역 상태 관리하기

 

  1. Zustand Store:  모달 컴포넌트 정보를 Map 객체로 저장
  2. 커스텀 훅: Modal을 open,close하는 함수를 만들어서 제공
  3. 사용자: 커스텀 훅의 open 함수와 close 함수를 사용해 모달을 조작

 

1. Zustand로 useOverlayStore 생성

overlays 변수: {id:itemElement} 형식으로 오버레이할 모달 컴포넌트를 맵 객체로 저장

addOverlay 함수: 추가할 오버레이 컴포넌트와  id값을 받아 overlays에 추가

deleteOverlay 함수: 오버레이한 컴포넌트 id값을 인자로 받아 delete 메서드로 overlays에서 제거

const useOverlayStore = create<OverlayStore>()(
  devtools((set) => ({
    overlays: new Map(),
    addOverlay: (overlayId, overlayItem) => {
      set((state) => {
        const overlays = new Map(state.overlays);
        overlays.set(overlayId, overlayItem);
        return { overlays };
      });
    },
    deleteOverlay: (overlayId) => {
      set((state) => {
        const overlays = new Map(state.overlays);
        overlays.delete(overlayId);
        return { overlays };
      });
    },
  }))
);

 

2. OverlayRoot 컴포넌트에서 오버레이 컴포넌트들 렌더링

overlays 변수에 오버레이할 컴포넌트 정보가 들어있으므로 가져와서 렌더링한다

다른 요소들보다 항상 위에 위치하도록 레이아웃 컴포넌트같은 최상위 위치에서 렌더링 시키는 것이 좋다

 

const OverlayRoot = () => {
  const { overlays } = useOverlayStore((state)=>state)
  return (
    <>
    {[...overlays.entries()].map(([id, element]) => (
          <Fragment key={id}>{element}</Fragment>
        ))}
    </>
  )
}

 

3. useOverlay 커스텀 훅 만들기

overlayId값은 오버레이 컴포넌트 구분을 위해 react의 useId() 함수를 사용

open 함수안에서 close() 함수를 넘겨 줌으로써,

사용자가 open 함수를 호출할때 close함수를 오버레이할 컴포넌트에 넣을 수 있다.

이렇게 하면 오버레이할 컴포넌트가 close함수를 호출할 수 있다.

const useOverlay = () => {
  const { addOverlay, deleteOverlay } = useOverlayStore(state => state);
  const overlayId = useId();
  return {
    open: (OverlayElement: OverlayElement) => {
      setTimeout(
        () => addOverlay(overlayId, 
        OverlayElement({ close: () => deleteOverlay(overlayId) })),
        0
      );
    },
    close: () => {
      deleteOverlay(overlayId);
    }
  };
};

 

4. useOverlay()를 사용해 Modal 열고 닫기

useOverlay의 open 함수를 사용하여 close 함수를 빼내서 AlertModal의 onClose 속성에 넘겨 준다.

AlertModal에서는 Modal의 바깥 영역을 클릭하거나 확인 버튼을 눌렀을때 onClose() 함수를 호출하게 된다.

  const overlay = useOverlay();

  const openPromiseAlert = () => new Promise((resolve) => {
    overlay.open(({close})=>(
      <AlertModal 
        onClose={() => {
          resolve(true)
          close()
        }}
      />
    ))
  })

 

5. Modal 컴포넌트 만들기

AlertModal에서는 Modal의 바깥 영역을 클릭하거나 확인 버튼을 눌렀을때 onClose() 함수를 호출한다.

const AlertModal = ({ onClose }:ModalProps) => {
  const ref = useRef<HTMLDivElement>(null);

  useClickOutside(ref,()=> onClose())

  return (
    <BaseModal>
      <div ref={ref} style={{border:'1px solid #ccc',padding:'30px'}}>
        <p>Alert Modal</p>
        <button onClick={() => onClose()}>Confirm</button>
      </div>
    </BaseModal>
  );
};