How To Print in React Using Iframes
👳🏽♂️
Ekam Singh / June 04, 2019
3 min read
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
Subscribe to the newsletter
Get emails from me about web development, tech, and early access to new articles.
- subscribers – 28 issues