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' }],
});