How to Print in React Using Iframes

-

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


Looking to Learn React & Next.js? 🏆

I've launched a new video course Mastering Next.js containing 55 jam-packed lessons. Pre-order now and get 50% off!

Mastering Next.js

Discuss on TwitterEdit on GitHub

Want More? Be Notified When I Post New Articles 🚀