Reputation: 521
I have a component which displays a data. I have to open this component in a new window on clicking a button/ link from a parent component.
export default class Parent extends Component {
construtor(props) {
super(props);
}
viewData = () => {
window.open('childcomponent.js','Data','height=250,width=250');
}
render() {
return (
<div> <a onclick={this.viewData}>View Data</a></div>
)
}
}
I dont know how to invoke another component and also display it in a new size specified window.
Actually I need to send a props to that child component with which it will fetch me the data from database and render it.
Upvotes: 32
Views: 107347
Reputation: 74176
You can use ReactDOM.createPortal
to render a component in a new window as David Gilbertson explains in his post:
class MyWindowPortal extends React.PureComponent { constructor(props) { super(props); // STEP 1: create a container <div> this.containerEl = document.createElement('div'); this.externalWindow = null; } render() { // STEP 2: append props.children to the container <div> that isn't mounted anywhere yet return ReactDOM.createPortal(this.props.children, this.containerEl); } componentDidMount() { // STEP 3: open a new browser window and store a reference to it this.externalWindow = window.open('', '', 'width=600,height=400,left=200,top=200'); // STEP 4: append the container <div> (that has props.children appended to it) to the body of the new window this.externalWindow.document.body.appendChild(this.containerEl); } componentWillUnmount() { // STEP 5: This will fire when this.state.showWindowPortal in the parent component becomes false // So we tidy up by closing the window this.externalWindow.close(); } }
Upvotes: 34
Reputation: 8892
This answer is based on David Gilbertson's post. It has been modified to work in Edge. To make this work in Edge div
and style
elements must be created with the window into which they will be rendered.
class MyWindowPortal extends React.PureComponent {
constructor(props) {
super(props);
this.containerEl = null;
this.externalWindow = null;
}
componentDidMount() {
// STEP 1: Create a new window, a div, and append it to the window. The div
// *MUST** be created by the window it is to be appended to (Edge only)
this.externalWindow = window.open('', '', 'width=600,height=400,left=200,top=200');
this.containerEl = this.externalWindow.document.createElement('div');
this.externalWindow.document.body.appendChild(this.containerEl);
}
componentWillUnmount() {
// STEP 2: This will fire when this.state.showWindowPortal in the parent component
// becomes false so we tidy up by just closing the window
this.externalWindow.close();
}
render() {
// STEP 3: The first render occurs before componentDidMount (where we open the
// new window) so container may be null, in this case render nothing.
if (!this.containerEl) {
return null;
}
// STEP 4: Append props.children to the container <div> in the new window
return ReactDOM.createPortal(this.props.children, this.containerEl);
}
}
The full modified source can be found here https://codepen.io/iamrewt/pen/WYbPWN
Upvotes: 10
Reputation: 60
For anyone having problem with this answer regarding copying styles and it's not working on production build.
In case you open new window using window.open('', ...)
, the new window will most likely have about:blank
as URL and won't find css files as they have relative paths. You need to set absolute path to href
attribute instead:
function copyStyles(src, dest) {
Array.from(src.styleSheets).forEach((styleSheet) => {
const styleElement = styleSheet.ownerNode.cloneNode(true);
styleElement.href = styleSheet.href;
dest.head.appendChild(styleElement);
});
Array.from(src.fonts).forEach((font) => dest.fonts.add(font));
}
Upvotes: 2
Reputation: 1634
If someone is having trouble adding the styles to your new window, the trick is to copy the whole DOM head from the parent to your new popup window. First you need to stablish the whole html skeleton
newWindow.document.write("<!DOCTYPE html");
newWindow.document.write("<html>");
newWindow.document.write("<head>");
newWindow.document.write("</head>");
newWindow.document.write("<body>");
newWindow.document.write("</body>");
newWindow.document.write("</html>");
// Append the new container to the body of the new window
newWindow.document.body.appendChild(container);
Now in the new window's DOM we have an empty head tag, we traverse the parent head tag and append its children to the new head.
const parentHead= window.document.querySelector("head").childNodes;
parentHead.forEach( item =>{
newWindow.document.head.appendChild(item.cloneNode(true)); // deep copy
})
And that's all. With the new window having the same head children than the parent, all your styles should be working now.
P.S. some styles might be a little stubborn and will not work, the hack that I found worked for those ones is to add a setTimeout in the componentDidMount or in the useEffect with the right time so as you can update your new head with the parent head. Something like this
setTimeout(() => {
updateHead();
}, 5000);
Upvotes: 0
Reputation: 1488
The upvoted answer works great!
Just leaving a function component version here in case people are searching for that in the future.
const RenderInWindow = (props) => {
const [container, setContainer] = useState(null);
const newWindow = useRef(null);
useEffect(() => {
// Create container element on client-side
setContainer(document.createElement("div"));
}, []);
useEffect(() => {
// When container is ready
if (container) {
// Create window
newWindow.current = window.open(
"",
"",
"width=600,height=400,left=200,top=200"
);
// Append container
newWindow.current.document.body.appendChild(container);
// Save reference to window for cleanup
const curWindow = newWindow.current;
// Return cleanup function
return () => curWindow.close();
}
}, [container]);
return container && createPortal(props.children, container);
};
Upvotes: 32
Reputation: 1875
Adding to the current answers - to copy the styles from the original window to the popup window, you can do:
function copyStyles(src, dest) {
Array.from(src.styleSheets).forEach(styleSheet => {
dest.head.appendChild(styleSheet.ownerNode.cloneNode(true))
})
Array.from(src.fonts).forEach(font => dest.fonts.add(font))
}
and then add
copyStyles(window.document, popupWindow.document);
after the call to window.open
and once the ref is initialized
Upvotes: 2
Reputation: 26615
You wouldn't open the component directly. You'll need a new page/view that will show the component. When you open the window, you'll then point it at the appropriate URL.
As for size, you provide it as a string in the third parameter of open, which you actually have correct:
window.open('http://example.com/child-path','Data','height=250,width=250');
Note, however, that browsers may, for a variety of reasons, not respect your width and height request. For that reason, it's probably a good idea to also apply appropriate CSS to get a space the right size in case it does open larger than you wanted.
Upvotes: 6