Issue
I'm using Nextjs + Typescript to make a toy project
and I'm having a problem when using createPortal to create a Modal.
Clicking the button does nothing.
This is probably because the value of ref.current is null.
why doesn't it work?
Plz help me.
HOC/Portal.tsx
import { useState, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
interface Props {
children: any;
}
const Portal: React.FC<Props> = ({ children }) => {
const ref = useRef<HTMLDivElement | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
ref.current = document.querySelector("overlay-root");
setMounted(true);
return () => setMounted(false);
}, []);
console.log(ref.current, "ref.current?");
console.log(mounted, "mounted");
return (mounted && ref.current) ? createPortal(children, ref.current!) : null;
};
export default Portal;
src/Components/layout/Header.tsx
...
{isLoginModal && (
<Portal>
<Login
onChangeLoginModal={onChangeLoginModal}
onChangeLoginState={onChangeLoginState}
isLoginState={isLoginState}
isLoginModal
/>
</Portal>
)}
...
pages/_documents.tsx
...
render() {
return (
<Html>
<Head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="target-densitydpi=device-dpi, user-scalable=0, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, width=device-width"
></meta>
<link
href="https://fonts.googleapis.com/css2?family=Dongle&family=Noto+Sans+KR:wght@500&family=Roboto:wght@500&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
{/* here */}
<div id="overlay-root"></div>
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
As a test, I tried the following simple case. but the result was the same.
src/Components/layout/Header.tsx
...
<Portal>
<div>Testing<div>
</Portal>
{isLoginModal && (
<Login
onChangeLoginModal={onChangeLoginModal}
onChangeLoginState={onChangeLoginState}
isLoginState={isLoginState}
isLoginModal
/>
)}
...
Solution
try this
import React, { useEffect, useRef } from "react";
import ReactDOM from "react-dom";
interface Props extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> {
/**
* the html tag you want to create.
* @default "section"
*/
section: keyof JSX.IntrinsicElements;
id:string;
className: string;
}
const Portal:React.FC<Props> = (props) => {
const { section, id,className, children, ...rest } = props;
const el = document.createElement(section);
const wrapper:React.RefObject<HTMLElement> = useRef(el);
useEffect(() => {
const current = wrapper.current as HTMLElement;
if(!current) return;
current.setAttribute("id", id);
current.setAttribute("class", className);
Object.keys(rest).forEach(attribute=>{
const val: keyof typeof rest = rest[(attribute as keyof typeof rest)];
current.setAttribute(attribute, val);
});
document.body.appendChild(current);
return () => {
document.body.removeChild(current);
};
}, [wrapper,id, className]);
if (!wrapper.current) {
return <>{null}</>;
}
return ReactDOM.createPortal(children, wrapper.current);
};
Portal.defaultProps = {
section:"section"
}
export default Portal;
Answered By - Ernesto
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.