React Portals로 모달 만들기

M_yeon·2024년 1월 19일
0

React

목록 보기
23/23
post-thumbnail

layout.tsx body태그 안에 <div id="portal" />코드를 작성해주자 id는 원하는 이름으로 해도 된다.

<html lang="ko">
  <body>
    {children}
    <div id="portal" />
  </body>
</html>

// ModalPortal.tsx
export default function ModalPortal({ children }: Props) {
  const [isCSR, setIsCSR] = useState(false);

  useEffect(() => {
    setIsCSR(true);
  }, []);

  if (typeof window === 'undefined') return <></>;
  if (!isCSR) return <></>;

  const node = document.getElementById('portal') as Element;
  return reactDom.createPortal(children, node);
}

위 파일에서 layout에 정의 해주었던 id="portal"를 호출하여 서버가 아닌 브라우저 환경이라면 createPortal로 node를 넣어준다.

return (
  <ModalPortal>
    <Modal>
      /*원하는 컴포넌트 호출*/
    </Modal>
  </ModalPortal>
);

컴포넌트를 ui에서 바로 노출해주어야할 때는 위처럼 하는데 (리스트 모달, 서명모달 등,,)

에러, 예 아니오의 기능만 해주면 될 때는 위처럼 하는게 굉장히 비효율적이라고 느껴진다.

한 페이지 안에서 여러 함수가 있을때 ModalPortal를 중복 호출해주어야하니 말이다!
그럴때는 createContext를 사용하여 provider를 만들고 modal에게 children을 넣어주는 방식으로 한다면 useModal이라는 hook을 사용하여 jsx를 쓰지 않고도 매번 호출할 수 있다

// modalProvaider.tsx
interface ModalContextType {
  content: Props | null;
  showModal: (props: Props) => void;
  hideModal: () => void;
}

const modalContext = createContext<ModalContextType | undefined>(undefined);

export function useModal(): ModalContextType {
  const context = useContext(modalContext);
  if (context === undefined) {
    throw new Error('useModal must be used within a ModalProvider');
  }
  return context;
}

export function ModalProvider({ children }: ModalProviderProps) {
  const [content, setContent] = useState<Props | null>(null);

  const showModal = (props: Props) => {
    setContent(props);
  };

  const hideModal = () => {
    setContent(null);
  };

  const value = useMemo(
    () => ({
      showModal,
      hideModal,
      content,
    }),
    [content],
  );

  return (
    <modalContext.Provider value={value}>{children}</modalContext.Provider>
  );
}

호출 방식

showModal({
  message: `~를 선택해주세요.`,
  buttons: [{ text: '확인', type: 'confirm' }],
});

0개의 댓글