Teleport react components in your page
Whenever you need to take content with functionality from a component and put it somewhere else on the page, you can use a react portal.
For example, if you have an input component that outputs some text in a div, you can pass that div to a portal and render it in another component or place on the page.
How portals basically work is as follows:
- you pass a react element and a DOM element to a function
createPortal
createPortal
attaches the react element to the DOM element- you take the DOM element and append it in a place in the DOM
This is useful when you have design constraints that require you to pass data and behavior from a react component to other places in the same page. Note that the source and the target need to be in the same page, meaning that the component that is passing the data should be in the same page as the target that is receiving that data.
Find a detailed video explanation here
For this, you can create a reusable component
import { FunctionComponent, PropsWithChildren, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
interface ComponentProps {
id: string
}
export const Portal: FunctionComponent<PropsWithChildren<ComponentProps>> = ({ id, children }):JSX.Element => {
const domElementToAppend = useRef<HTMLDivElement>();
const [isClientSide, setIsClientSide] = useState(false);
useEffect(() => {
const target = document.getElementById(id);
domElementToAppend.current = document.createElement('div');
if (!domElementToAppend.current) {
return;
}
target?.appendChild(domElementToAppend.current);
setIsClientSide(true);
return () => {
if (domElementToAppend.current) {
target?.removeChild(domElementToAppend.current);
}
};
}, []);
return (
<>
{
(isClientSide && domElementToAppend.current) && createPortal(children, domElementToAppend.current)
}
</>
);
};
You need to create an html element, domElementToAppend
. This is a ref element, because you need to initialize it in the useEffect
hook as a DOM component, and then, the actual data is passed from the createPortal
call in the template.
For server side rendered pages you need to make sure that the createPortal
is called only when you are on the client side and after you have initialized domElementToAppend
as a DOM component, else you won't see anything in the template at first render.
The target
is an actual DOM element that is already in the page. This is the place where you will attach the data from the component that sends data to the portal.
Then you use this in your components like so:
import React, { ChangeEvent, FunctionComponent, useState } from 'react';
import { Portal } from '../shared/Portal';
export const InputToDiv: FunctionComponent<any> = (): JSX.Element => {
const [text, setText] = useState('');
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setText(event.target.value);
};
//
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
console.log('clicked!!!!');
};
return (
<div onClick={handleClick}>
<input type="text" onChange={handleChange}/>
<Portal id="my-input-portal">
<div>
<strong>Typed text is {text}</strong>
</div>
</Portal>
</div>
);
};
And then, in the actual page you need both the component and the portal
//...
<InputToDiv />
//...
<div id="my-input-portal"></div>
You can find the code here
Subscribe to my newsletter
Read articles from Mihai Marinescu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by