How To Print in React Using Iframes

JW

Jonathan Wong / June 04, 2019

4 min read––– views

You want to print the contents of a page. If you use the standard browser API, this will print the entirety of the current page. In this scenario, you can use the print media query to show/hide certain parts of the page.

@media print {
  header,
  footer {
    display: none;
  }
}

What if you want to print a different page from the current page?

Use Case#

Let's say you're building an e-commerce website. Users have requested they want the option to print a receipt after their purchase. You don't want to mess with PDFs, so you'd like to use the web to create the structure and styling for you.

After completing their payment, users should have the option to click a "print receipt" button. This only loads the contents of the receipt and invokes the browser print dialog for them. It should be reusable so we can reference the same receipt from their order history.

Using React & Iframes#

Why do we need iframes to solve this problem? Our use case requires loading only the receipt and nothing else. It should be available in other parts of the application as well.

We can create an iframe which loads the contents of our receipt. Once the content has finished loading, we can invoke the browser print API. This means it will only print what we want and allows us to reuse this component/route in other places.

Example#

First, let's create an iframe that routes to our receipt.

<iframe
  id="receipt"
  src="/payment/receipt"
  style={{ display: 'none' }}
  title="Receipt"
/>

We need to ensure the contents of that route have loaded, otherwise the page will be blank when trying to print. We can communicate with the iframe from the receipt route to let it know we've finished loading.

parent.postMessage({ action: 'receipt-loaded' });

Finally, we need a way to invoke the print dialog. Let's create a component which sets up an event listener for the receipt-loaded message from the iframe receipt route. When the route has finished loading, we can click the button.

import React, { useState, useEffect } from 'react';

function PaymentConfirmation() {
  const [isLoading, setIsLoading] = useState(true);

  const handleMessage = (event) => {
    if (event.data.action === 'receipt-loaded') {
      setIsLoading(false);
    }
  };

  const printIframe = (id) => {
    const iframe = document.frames
      ? document.frames[id]
      : document.getElementById(id);
    const iframeWindow = iframe.contentWindow || iframe;

    iframe.focus();
    iframeWindow.print();

    return false;
  };

  useEffect(() => {
    window.addEventListener('message', handleMessage);

    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, []);

  return (
    <>
      <iframe
        id="receipt"
        src="/payment/receipt"
        style={{ display: 'none' }}
        title="Receipt"
      />
      <button onClick={() => printIframe('receipt')}>
        {isLoading ? 'Loading...' : 'Print Receipt'}
      </button>
    </>
  );
}

export default PaymentConfirmation;

Note: The above example uses hooks which are only available in React >16.8.0. If you're using an older version of React, use a class instead.

import React from 'react';

const printIframe = (id) => {
  const iframe = document.frames
    ? document.frames[id]
    : document.getElementById(id);
  const iframeWindow = iframe.contentWindow || iframe;

  iframe.focus();
  iframeWindow.print();

  return false;
};

class PaymentConfirmation extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoading: true
    };

    this.handleMessage = this.handleMessage.bind(this);
  }

  componentWillMount() {
    window.addEventListener('message', this.handleMessage);
  }

  componentWillUnmount() {
    window.removeEventListener('message', this.handleMessage);
  }

  handleMessage(event) {
    if (event.data.action === 'receipt-loaded') {
      this.setState({
        isLoading: false
      });
    }
  }

  render() {
    return (
      <>
        <iframe
          id="receipt"
          src="/payment/receipt"
          style={{ display: 'none' }}
          title="Receipt"
        />
        <button onClick={() => printIframe('receipt')}>
          {this.state.isLoading ? 'Loading...' : 'Print Receipt'}
        </button>
      </>
    );
  }
}

export default PaymentConfirmation;

Result#

Printing in React using iframes

React 2025

Build and deploy a modern Jamstack application using the most popular open-source software.

Discuss on TwitterEdit on GitHub
Spotify album cover

/tools/photos