react-to-print
react-to-print copied to clipboard
Prints only once in the latest macOS Safari
Using the official example: https://codesandbox.io/s/rzdhd First time I try to print the printing dialog shows up as expected. If I cancel it and try to print the second time, a popup is displayed saying that the document is already sent to printer. If I click OK, the printing dialog shows up with the page to print being empty.
macOS 11.4, Safari 14.1.1
I have the same issue +1 with same version, but it happen not only with this lib but with every window.print()
As far as I know, the same problem already happened before. I think it was due to the Safari popup not blocking JS code execution, causing popular libraries to remove the elements from the page before they were actually sent to print after user clicks Confirm.
Seems like the workarounds are not working anymore in new versions of Safari. This problem also present in Print.is library.
Or maybe it's just a new issue.
It seems something has changed with Safari 14.1 that breaks this. I'll look for a solution but there may not be one. I did find that refreshing the page solves the problem, so maybe as a hopefully temporary workaround you could refresh the page in the afterPrint
callback. Unfortunately there is no way to know in this callback if the user pressed "Cancel" or "Print". If anyone learns more please let me know
It seems something has changes with Safari 14.1 that breaks this. I'll look for a solution but there may not be one. I did find that refreshing the page solves the problem, so maybe as a hopefully temporary workaround you could refresh the page in the
afterPrint
callback. Unfortunately there is no way to know in this callback if the user pressed "Cancel" or "Print". If anyone learns more please let me know
Thank you for looking into this. Unfortunately, page refresh is usually not an option for React views, but I don't know any better solution.
Any update on this?
Hello. I don't have an update yet unfortunately. I don't know what is causing the second print to show distorted. We previously were able to avoid the Safari modal completely, but it seems the Safari change is now impacting multiple printing libraries such as Print.js
, not just this one. I am actively looking for solutions
Hey @MatthewHerbst I am on Safari Version 15.0 (15612.1.29.41.4, 15612) and this issue still persists. Would like to share a work around I was able to come up. The workaround here works in lieu of useReactToPrint
hook
I was inspired by this stackoverflow thread whereby I open another popup window and render the div (componentRef) contents. Then using the popup window document to print instead of main document.
Here's high level idea if anyone wants to replicate:
Code
```javascript // browser detection const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); ...// Print handler for Safari
const getSafariPrintHandler = (componentRef, documentTitle) => () => {
const popupWindow = window.open(
url,
documentTitle,
toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=750, height=450
);;
const printDoc = popupWindow.document;
printDoc.write(<!DOCTYPE> <html> <head> <title>${documentTitle}</title> </head> <body> ${componentRef.current.innerHTML} </body> </html>
);
// apply the default print styles here: https://github.com/gregnb/react-to-print/blob/master/src/index.tsx#L29 const styleEl = printDoc.createElement('style'); styleEl.appendChild(printDoc.createTextNode(pagePrintStyleDefault)); printDoc.head.appendChild(styleEl);
// copy styles logic from react-to-print copyStyles(printDoc);
// clean up printDoc.close(); popupWindow.print(); popupWindow.close(); }; .... // finally, the wrapper hook export const usePrint = ({ componentRef, ...otherConfig}) => { const handlePrint = useReactToPrint({ content: () => componentRef.current, ...otherConfig }); if (isSafari) { return getSafariPrintHandler(componentRef); }
return handlePrint; };
</details>
The usage is similar to `useReactToPrint` hook documented in this module.
Thanks @vikramkamath! I'll try and see if I can adapt some of that into the library. PRs are also always welcome 😄
Related: https://github.com/crabbly/Print.js/issues/528
@vikramkamath Do you have working version of code ?
Can you please share here
Thanks in advance
@darshak369 I have created a sandbox which does not use useReactToPrint
hook but only bits of code from react-to-print
module. You can download the files as zip locally and run it.
Note: This demo won't print on any other browser as I am only checking for Safari. For other browsers, I use useReactToPrint
out of the box.
Let me know if you encounter any issues.
@vikramkamath Thanks for your solution it is working fine I have encountered with other issue in your code which is related to document styles
I am facing <link>
warning which you have write in else block. I want to add link tag to my target document how will i do that in your code.
Is their any ways to add link tag also to my document so my style also replicate while downloading PDF using safari Browser ?
@darshak369 in my case I wasn't relying on <link href ... />
for styles hence I handled it like that. The warning is appropriate in your case as the print doc relies on <link>
tags for styles. You can add the following block in the copyStyles()
that should apply styles from <link>
tags: https://github.com/gregnb/react-to-print/blob/master/src/index.tsx#L441-L468
tldr; The copyStyles()
was created from src/index.tsx
and I skipped loading any styles from the <link>
part.
@vikramkamath Thanks for the information
I am able to load the <link>
tag correctly now it is not showing me any kind of warning related to the <link>
.
This is the code that i had modified in copyStyles()
, is there something else we have to add in getSafariPrintHandler()
?
Code
const copyStyles = targetDocument => {
const headEls = document.querySelectorAll("style, link[rel='stylesheet']");
for (let i = 0, headElsLen = headEls.length; i < headElsLen; i += 1) {
const node = headEls[i];
if (node.tagName === 'STYLE') {
console.log('Loading Style Tag...');
const styleEl = targetDocument.createElement(node.tagName);
const { sheet } = node;
if (sheet) {
let styleCSS = '';
for (let j = 0, cssLen = sheet.cssRules.length; j < cssLen; j += 1) {
if (typeof sheet.cssRules[j].cssText === 'string') {
styleCSS += `${sheet.cssRules[j].cssText}\r\n`;
}
}
styleEl.appendChild(targetDocument.createTextNode(styleCSS));
targetDocument.head.appendChild(styleEl);
}
} else if (node.tagName === 'LINK' && node.getAttribute('href')) {
console.log('Loading Link Tag...');
const newHeadEl = targetDocument.createElement(node.tagName);
for (let j = 0, attrLen = node.attributes.length; j < attrLen; j += 1) {
// eslint-disable-line max-len
const attr = node.attributes[j];
if (attr) {
newHeadEl.setAttribute(attr.nodeName, attr.nodeValue || '');
}
}
targetDocument.head.appendChild(newHeadEl);
} else {
console.log('Warning.....');
// eslint-disable-next-line no-console
console.warn(
'Encountered invalid HTML. This can cause problems in many browsers, and so the <link> was not loaded. The <link> is:',
node,
);
}
}
};
After clicking on the button nothing happens
@darshak369 you forgot to wrap the <link>
node code in the else
block:
if (node.tagName === 'STYLE') {
// This code is correct
} else { // This is what you are forgetting
// All the code below the `<link> nodes` comment needs to be in here
}
Here is the new update I am able to load the <link>
and <style>
tags correctly. I had debugged the code and I found that the document has <link>
and <style>
tags inside <head>
tag.
Logs of link and style tags -
[Log] print doc #document
<!DOCTYPE >
<html>
<head>
<title>Darshak-Shark (Copy)-Sourcing</title>
<style>…</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://domain.com/vendors.css" rel="stylesheet">
<link href="https://domain.com/styles.css" rel="stylesheet">
<style>…</style>
<style>…</style>
<style></style>
<style>@media print { @page { size: portrait; } html, body { height: 99%; } }</style>
</head>
<body>…</body>
</html>
@vikramkamath @MatthewHerbst any idea why is this happening although it is calling function correctly yet not opening a pop-up window.
@darshak369 if you could make a working codesandbox that would be very helpful please to help debugging, it's very difficult to do it with comments
@MatthewHerbst Thanks for your response
It will be very time consuming to convert code into codesandbox for me.
I have downloaded those HTML codes to my system and run them locally apparently It solved my previous issue related to formatting. the new issue that I was facing now is window pop up closes immediately after opening in Chrome Browser and Safari as well.
I was going through the https://github.com/gregnb/react-to-print/blob/master/src/index.tsx#L441-L468 code from repo.
I have found I forget to write markLoaded()
function, I am not sure what to add in copyStyle()
function.
Can you please suggest to me which code will solve my issue!
Above Comment Has CopyStyle()
function which run fine with <style>
tag.
Thanks for your time
@darshak369 found any solution ? After trying with -
if (node.tagName === 'STYLE') {
// This code is correct
} else { // This is what you are forgetting
// All the code below the `<link> nodes` comment needs to be in here
}
the window pop-up and closes immediately after opening in Safari.
if I comment copystyles(), the print dialog is being open with HTML, but it excludes all styles.
const styleEl = printDoc.createElement("style");
styleEl.appendChild(printDoc.createTextNode(pagePrintStyleDefault));
printDoc.head.appendChild(styleEl);
// copyStyles(printDoc);
printDoc.close();
popupWindow.print();
Hey folks.
I came across this issue and figured out a way to work around it by reading some threads about the topic
I created a hook that attaches a div element to a new browser window and runs the print command from the new windows. By doing it, if the users cancel the printing flow, the next time they hit the button the safari dialog asking if they really want to print it won't appear and the issue doesn't happen.
export const useBrowserPrint = () => {
const print = (element: HTMLDivElement, updateCallerKeyCallback: () => void) => {
const newWindow = window.open()
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>')
newWindow!.document.body.appendChild(element)
const parentHead = window.document.querySelector('head')!.childNodes
parentHead.forEach((item) => {
newWindow!.document.head.appendChild(item.cloneNode(true))
})
setTimeout(() => {
newWindow!.document.close()
newWindow!.focus()
newWindow!.print()
newWindow!.close()
}, 250)
updateCallerKeyCallback()
}
return { print }
}
This callback function is to update the caller key and rerender it on the screen cuz it's been removed out of the DOM when attached to the new window. This is the code present in the component I want to print:
const { print } = useBrowserPrint()
const [printableKey, updatePrintableKey] = useState<number>(0)
const forcePrintableKeyUpdate = useCallback(() => updatePrintableKey(Math.random()), [])
const printableRef = useRef<HTMLDivElement>(null)
const handlePrint = () => print(printableRef.current!, forcePrintableKeyUpdate)
return (
<div key={printableKey} className="container self-center">
<div className="flex flex-col items-center">
<ActionBar onClick={handlePrint} />
<div ref={printableRef} className="w-full flex flex-col items-center mt-4">
....
The code is not using the library but maybe it can help on defining a definitive solution. It works fine on Safari, Firefox and Chrome
This issue appears resolved by Apple using Safari 16.4, MacOS 13.3
We observe same issue with Safari 17.3.1, MacOS 14.3.1
@drekinov could you please share steps to reproduce, and let me know what version of react-to-print
you are using? I'm testing react-to-print
v2.15.0 and the same MacOS/Safari versions, and am unable to reproduce the issue.
I do see this message pop up, but clicking "Print" or clicking "Cancel" and then printing again (which brings up the box again) do seem to work fine
Ah, sorry we had bothered you. Probably did not understand the root issue here. We observe the same as you did. We are not mac / safari primary users. We got customer complain that this message appear. It is a bit annoying because of Single page application each print after first cancel trigger it.. Comparing with chrome and other browsers we thought that message is the issue discussed here.
Ah, no problem. And yeah, nothing we can do about Safari popping that message box unfortunately. I don't fully understand why they do it honestly, it's not like it's preventing malicious behavior somehow 🤷 Hopefully one day there is a more formal Print API that solves these browser-specific oddities. Please do keep letting us know about dev/customer feedback though, it's very much appreciated!