31

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.

Vandhana
  • 501
  • 2
  • 7
  • 14

7 Answers7

33

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();
  }
}
CD..
  • 72,281
  • 25
  • 154
  • 163
  • 7
    This solution wrapped is up nicely in an npm module here: https://www.npmjs.com/package/react-new-window – Dane Jordan Apr 20 '19 at 02:01
  • 1
    There is a problem with the styles, since those are not applyed to all the components in the new window. Could be related with the fact that the head of the new dom is now empty, mean while the head of the react app is totally filled with styles and scripts the same react app is filling/updating. – Pepe Alvarez Aug 30 '21 at 15:59
28

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);
};
rob-gordon
  • 1,419
  • 3
  • 20
  • 38
  • 1
    How can we carry application styles and data. – Rahul Oct 30 '20 at 01:26
  • @Rahul see [here](https://stackoverflow.com/a/65699428/1380370) – amiregelz Jan 13 '21 at 09:52
  • 2
    @rob-gordon Any idea why [this](https://stackoverflow.com/a/65699428/1380370) doesn't work in my create-react-app production build? – amiregelz Jan 21 '21 at 16:47
  • 1
    @amiregelz Hard to guess w/o seeing – but I did learn that if you're doing SSR sometimes `document.createElement` won't be available on the server and it can fail. In that case you can create the div inside the `useEffect`. No idea if that's what's happening in your case though – rob-gordon Jan 21 '21 at 16:54
  • @rob-gordon This solution is working fine for me. But when I closed the newly opened window and clicked on the button again to open the new window then nothing happened. Whenever I clicked on the button new window should be opened. –  rocco Nov 15 '22 at 11:17
  • 1
    @rocco It's a little more complicated when you want to control the open/close state of the window. Have a look at https://stackblitz.com/edit/react-zpazdi?file=src/App.js – rob-gordon Nov 16 '22 at 01:49
10

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

Ryan Taylor
  • 8,740
  • 15
  • 65
  • 98
  • 3
    I had trouble with it rendering a 2nd time after the window had mounted. Nothing was telling it to rerender besides when this.containerEl was empty. To fix I added a setState({mopunted:true}) to the end of componentDidMount. – Kenny Hammerlund Apr 03 '19 at 18:49
  • I tried with above code example steps. still its not working for me with IE edge. Is anyone other solutions? – Karthik Jun 24 '20 at 14:14
  • 2
    I can load a react component in a popup using this. But the styles are not carried forward. The component loaded in the popup doesn't contain the styles defined in the application. Is there a way to pass styles into the popup?? – Vignesh M Oct 08 '20 at 10:22
5

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.

samanime
  • 25,408
  • 15
  • 90
  • 139
2

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

amiregelz
  • 1,833
  • 7
  • 25
  • 46
2

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));
}
Dr. Onix
  • 60
  • 9
0

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);
Pepe Alvarez
  • 1,218
  • 1
  • 9
  • 15