How to print React component on click of a button?
Asked Answered
T

13

37

How can I print only one component on click of a button.

I know this solution:

window.frames["print_frame"].window.focus();
window.frames["print_frame"].window.print();
$('.print_frame').remove();

But React doesn't want to work with a frame.

Any solutions? Thank you.

Threephase answered 9/5, 2015 at 2:45 Comment(0)
A
58

There is kind of two solutions on the client. One is with frames like you posted. You can use an iframe though:

var content = document.getElementById("divcontents");
var pri = document.getElementById("ifmcontentstoprint").contentWindow;
pri.document.open();
pri.document.write(content.innerHTML);
pri.document.close();
pri.focus();
pri.print();

This expects this html to exist

<iframe id="ifmcontentstoprint" style="height: 0px; width: 0px; position: absolute"></iframe>

The other solution is to use the media selector and on the media="print" styles hide everything you don't want to print.

<style type="text/css" media="print">
   .no-print { display: none; }
</style>

Last way requires some work on the server. You can send all the HTML+CSS to the server and use one of many components to generate a printable document like PDF. I've tried setups doing this with PhantomJs.

Aintab answered 9/5, 2015 at 7:15 Comment(8)
I check it because I use it right now. But it's not a good solution because this will be blocked in most browsers (Firefox and Chrome) because it behaves like a popup.Threephase
I'm not sure I understand. None of the solutions I've mentioned will trigger the popup blocker. Using CSS, will not, using an existing iframe will not and using the server method will definitely not. Can you explain what you mean?Aintab
Which of the solutions trigger this?Aintab
Using window.open will definitely trigger the popup blocker, but using an iframe like in the example above, and opening the document should not.Aintab
It works like a charm, thanks @Emil Ingerslev for the solution. I am using the first solution, the only catch is one-pixel dot visible on front end.Corkboard
Works exactly the way I wanted . Thanks.Synapse
it shows all the contents but the CSS is missingPolicewoman
Shows almost empty contents, maybe because I have a canvas. Anyway to make this work with a canvas?Cathicathie
P
18

I was looking for a simple package that would do this very same task and did not find anything so I created https://github.com/gregnb/react-to-print

You can use it like so:

 <ReactToPrint
   trigger={() => <a href="#">Print this out!</a>}
   content={() => this.componentRef}
 />
 <ComponentToPrint ref={el => (this.componentRef = el)} />
Poucher answered 21/3, 2018 at 6:43 Comment(7)
@Gregory Nowakowski - Can this library handle css passed by css modules? If so, how?Apiarist
@Gregory Nowakowski, I want to print multiple receipt from multiple component in single click. Does it work?Cockle
@MuthukumarMarichamy you can whatever inside the reference given <div ref={el => (this.componentRef = el)}> <component-one> <component-two> </div>Cherri
I am trying to use this. But on Click nothing is happeningEllerey
@VinitKhandelwal are you using bootstrap or others css library? if so make sure you read the documentation how the ref is used. some lib using innerRef instead of refHeterozygous
works with classbased function & requires reduxPolicewoman
I was trying to use this lovely solution, but my button is on another component (something like "buttons panel") while the ComponentToPrint best fits on the "main window". So far I haven't found any elegant approach to marry it with react-to-print , either have to move everything to "buttons panel" which looks ugly, or integrate "button panel" directly into "main window" which is a bit less ugly, but still brings more mess into the original code. Any ideas are welcome! :)Mahan
M
12

You'll have to style your printout with @media print {} in the CSS but the simple code is:

export default class Component extends Component {

    print(){
        window.print();
    }


  render() {

  ...
  <span className="print"
              onClick={this.print}>
    PRINT
    </span>

  } 
}

Hope that's helpful!

Miltiades answered 25/5, 2016 at 15:58 Comment(2)
Your code is working fine in Chrome and Firefox. However, in Safari is not doing nothing in my case? Do you know why? I see here that the code is the same and it works in Safari... w3schools.com/jsref/met_win_print.aspIntrigante
Hi @AralRoca I'm not sure but it could be a weird thing with Safari, found this, probably something I would try: #44926402Jacksnipe
F
7

On 6/19/2017 This worked perfect for me.

import React, { Component } from 'react'

class PrintThisComponent extends Component {
  render() {
    return (
      <div>
        <button onClick={() => window.print()}>PRINT</button>
        <p>Click above button opens print preview with these words on page</p>
      </div>
    )
  }
}

export default PrintThisComponent
Furrier answered 20/6, 2017 at 0:24 Comment(3)
While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value.Topic
This doesn't work for this problem- your proposed solution prints the entire page, not a single component as asked.Ling
It prints the whole page and not one component.Corkboard
I
5

If you're looking to print specific data that you already have access to, whether it's from a Store, AJAX, or available elsewhere, you can leverage my library react-print.

https://github.com/captray/react-print

It makes creating print templates much easier (assuming you already have a dependency on react). You just need to tag your HTML appropriately.

This ID should be added higher up in your actual DOM tree to exclude everything except the "print mount" below.

<div id="react-no-print"> 

This is where your react-print component will mount and wrap your template that you create:

<div id="print-mount"></div>

An example looks something like this:

var PrintTemplate = require('react-print');
var ReactDOM = require('react-dom');
var React = require('react');

var MyTemplate = React.createClass({
    render() {
        return (
            <PrintTemplate>
                <p>Your custom</p>
                <span>print stuff goes</span>
                <h1>Here</h1>
            </PrintTemplate>
        );
    }
});

ReactDOM.render(<MyTemplate/>, document.getElementById('print-mount'));

It's worth noting that you can create new or utilize existing child components inside of your template, and everything should render fine for printing.

Intratelluric answered 13/8, 2016 at 22:1 Comment(5)
This should demonstrate it. Just watch the result window when you hit Ctrl + P to print. jsfiddle.net/captray/69z2wepo/68966Intratelluric
what if you still need the document.getElementById('root')?Miocene
@FiddleFreak Not sure I understand your question? It's been three years since I posted this, so I might need more context.Intratelluric
my question is doesn't react have it so you can only have one document.getElementById() since it is considered to be a single page app? What if you need something like ReactDOM.render(<MyTemplate/>, document.getElementById('print-mount'), document.getElementById('root')), would that work?Miocene
Still not exactly sure what you’re trying to do, but can have multiple mount points in your markup. This code is also dated, but you might want to view the source and understand what it’s doing, as I’m not sure this meets your needs.Intratelluric
C
5

The solution provided by Emil Ingerslev is working fine, but CSS is not applied to the output. Here I found a good solution given by Andrewlimaza. It prints the contents of a given div, as it uses the window object's print method, the CSS is not lost. And there is no need for an extra iframe also.

var printContents = document.getElementById("divcontents").innerHTML;
var originalContents = document.body.innerHTML;
document.body.innerHTML = printContents;
window.print();
document.body.innerHTML = originalContents;

Update 1: There is unusual behavior, in chrome/firefox/opera/edge, the print or other buttons stopped working after the execution of this code.

Update 2: The solution given is there on the above link in comments:

.printme { display: none;}
@media print { 
    .no-printme  { display: none;}
    .printme  { display: block;}
}

<h1 class = "no-printme"> do not print this </h1>    
<div class='printme'>
  Print this only 
</div>    
<button onclick={window.print()}>Print only the above div</button>
Corkboard answered 17/4, 2020 at 15:1 Comment(2)
So far, this was the one that worked for me. But the issue I'm seeing is when I click cancel and click the print button again... nothing opens up.Miocene
It's going to override the document and rest of the js can't operate properly.Pantie
T
3

First want to credit @emil-ingerslev for an awesome answer. I tested it and it worked perfectly. There were two things however I wanted to improve.

  1. I didn't like having to already have <iframe id="ifmcontentstoprint" style="height: 0px; width: 0px; position: absolute"></iframe> already in the dom tree.
  2. I wanted to create a way to make it reusable.

I hope this makes others happy and saves a few minutes of life. Now go take those extra minutes and do something nice for someone.

function printPartOfPage(elementId, uniqueIframeId){
    const content = document.getElementById(elementId)
    let pri
    if (document.getElementById(uniqueIframeId)) {
        pri = document.getElementById(uniqueIframeId).contentWindow
    } else {
        const iframe = document.createElement('iframe')
        iframe.setAttribute('title', uniqueIframeId)
        iframe.setAttribute('id', uniqueIframeId)
        iframe.setAttribute('style', 'height: 0px; width: 0px; position: absolute;')
        document.body.appendChild(iframe)
        pri = iframe.contentWindow
    }
    pri.document.open()
    pri.document.write(content.innerHTML)
    pri.document.close()
    pri.focus()
    pri.print()
}

EDIT 2019-7-23: After using this more, this does have the downside that it doesn't perfectly render react components. This worked for me when the styling was inline but not when handled by styled-components or some other situations. If I come up with a foolproof method I will update.

Tayler answered 22/3, 2019 at 20:20 Comment(0)
K
0

Just sharing what worked in my case as someone else might find it useful. I have a modal and just wanted to print the body of the modal which could be several pages on paper.

Other solutions I tried just printed one page and only what was on screen. Emil's accepted solution worked for me:

https://mcmap.net/q/413315/-how-to-print-react-component-on-click-of-a-button

This is what the component ended up looking like. It prints everything in the body of the modal.

import React, { Component } from 'react';
import {
    Button,
    Modal,
    ModalBody,
    ModalHeader
} from 'reactstrap';

export default class TestPrint extends Component{
    constructor(props) {
        super(props);
        this.state = {
            modal: false,
            data: [
                'test', 'test', 'test', 'test', 'test', 'test', 
                'test', 'test', 'test', 'test', 'test', 'test', 
                'test', 'test', 'test', 'test', 'test', 'test',
                'test', 'test', 'test', 'test', 'test', 'test',
                'test', 'test', 'test', 'test', 'test', 'test',
                'test', 'test', 'test', 'test', 'test', 'test',
                'test', 'test', 'test', 'test', 'test', 'test',
                'test', 'test', 'test', 'test', 'test', 'test'            
            ]
        }
        this.toggle = this.toggle.bind(this);
        this.print = this.print.bind(this);
    }

    print() {
        var content = document.getElementById('printarea');
        var pri = document.getElementById('ifmcontentstoprint').contentWindow;
        pri.document.open();
        pri.document.write(content.innerHTML);
        pri.document.close();
        pri.focus();
        pri.print();
    }

    renderContent() {
        var i = 0;
        return this.state.data.map((d) => {
            return (<p key={d + i++}>{i} - {d}</p>)
        });
    }

    toggle() {
        this.setState({
            modal: !this.state.modal
        })
    }

    render() {
        return (
            <div>
                <Button 
                    style={
                        {
                            'position': 'fixed',
                            'top': '50%',
                            'left': '50%',
                            'transform': 'translate(-50%, -50%)'
                        }
                    } 
                    onClick={this.toggle}
                >
                    Test Modal and Print
                </Button>         
                <Modal 
                    size='lg' 
                    isOpen={this.state.modal} 
                    toggle={this.toggle} 
                    className='results-modal'
                >  
                    <ModalHeader toggle={this.toggle}>
                        Test Printing
                    </ModalHeader>
                    <iframe id="ifmcontentstoprint" style={{
                        height: '0px',
                        width: '0px',
                        position: 'absolute'
                    }}></iframe>      
                    <Button onClick={this.print}>Print</Button>
                    <ModalBody id='printarea'>              
                        {this.renderContent()}
                    </ModalBody>
                </Modal>
            </div>
        )
    }
}

Note: However, I am having difficulty getting styles to be reflected in the iframe.

Kea answered 20/4, 2018 at 17:7 Comment(1)
You can gather up all the style tags and inject them like this: const styleTags = Array.from(document.getElementsByTagName("style")).map(x => x.outerHTML).join(""); pri.document.write(styleTags);Spellbound
T
0

Perfect solution:

class PrintThisComponent extends Component {
  render() {
    return (
      <div>
      <button className="btn btn-success btn-lg"
        onClick={() => window.print()}>
        PRINT
      </button>
      </div>
    )
  }
}
export default PrintThisComponent;

Just go anywhere in your React file and do:

import PrintThisComponent from 'the file having above code';

<PrintThisComponent />
Therapsid answered 11/5, 2021 at 15:32 Comment(1)
Thanks for your answer but it's more or less the same as sara-inés-calderón's and Clay_F's solution. Besides that, your code can't be copy-pasted since it contains a semicolon in the wrong place and the instruction should not be part of the code.Unsphere
L
0

What's Up! I made this... maybe it help someone

It will wait to be loaded to print, It's the magic... Dont forget to change the CSS link


    /**
     * PRINT A DIV ELEMENT
     * @param {HTMLDivElement} div
     */
    function print(div) {
        let innerHTML = `<!DOCTYPE html>
        <html lang="pt-BR">
        <head>   
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
        <link href="/css/app.css" rel="stylesheet" />
        </head>
        <body>`;
        innerHTML += div.innerHTML + "</body></html>";
        console.log(innerHTML);
        let fakeIFrame = window.document.createElement("iframe");
        document.body.appendChild(fakeIFrame);
        let fakeContent = fakeIFrame.contentWindow;
        fakeContent.document.open();
        fakeContent.document.write(innerHTML);
        fakeContent.document.close();
        fakeContent.focus();
        fakeIFrame.addEventListener("load", () => {
            fakeContent.print();
        });
    }
Lewak answered 14/6, 2022 at 12:58 Comment(3)
Your description above is not understandable. Please add dots between sentenses and try to include only information which can help other to understand the implementation.Dwyer
Sorry, but What was not understandble?Lewak
If you publish your code, it is good practice to tell how it works. None of your accompanying text explain this. Take this as advice not critics. For some more tips on how to write good answer read this - stackoverflow.com/help/how-to-answer. tcDwyer
A
0

//if you want to print particular div
`

@media print {
  body * {
    visibility: hidden;
  }
  #divToPrint {
    visibility: visible;
  }
}

function Download() { const handleDownloadClick = () => { const content = 'Get me downloaded'; const blob = new Blob([content], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'download.txt'; a.style.display = 'none'; document.body.appendChild(a); a.click();

    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  };

  return (
    <div>
      <button onClick={handleDownloadClick}>Download File</button>
    </div>
  );
}
`
Augustinaaugustine answered 6/10, 2023 at 2:2 Comment(0)
C
0

Conditional Rendering with perfectly set delays works.

const [isPrinting, setIsPrinting] = useState(false);

const printItems = () => {
    setIsPrinting(true);
    setTimeout(() => {
        window.print();
    }, 500);
    setTimeout(() => {
        setIsPrinting(false);
    }, 2000);
  };

  {!isPrinting? <button>Print</button> : null}
Chainplate answered 27/2 at 3:48 Comment(0)
P
0

There is kind of solution on the client side without creating an additional Iframe. It's also not going to override the document and rest of the js will operate properly.

  const printHandler = () => {
    const bodyElement = document.getElementsByTagName('body')[0];
    bodyElement.classList.add('printing');
    window.print();
    bodyElement.classList.remove('printing');
  };

And these are CSS classes

.printing {
  // visibility:hidden;
}

.printView {
  visibility:visible;
}

.printing .printView {
  /* You can have any CSS here to make the view better on print */
  position:absolute;
  top:0;
}
Pantie answered 1/4 at 14:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.