39

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.

13 Answers 13

60

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.

Sign up to request clarification or add additional context in comments.

7 Comments

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.
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?
Which of the solutions trigger this?
Using window.open will definitely trigger the popup blocker, but using an iframe like in the example above, and opening the document should not.
it shows all the contents but the CSS is missing
|
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)} />

7 Comments

@Gregory Nowakowski - Can this library handle css passed by css modules? If so, how?
@Gregory Nowakowski, I want to print multiple receipt from multiple component in single click. Does it work?
@MuthukumarMarichamy you can whatever inside the reference given <div ref={el => (this.componentRef = el)}> <component-one> <component-two> </div>
I am trying to use this. But on Click nothing is happening
@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 ref
|
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!

2 Comments

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.asp
Hi @AralRoca I'm not sure but it could be a weird thing with Safari, found this, probably something I would try: stackoverflow.com/questions/44925902/…
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

3 Comments

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.
This doesn't work for this problem- your proposed solution prints the entire page, not a single component as asked.
It prints the whole page and not one component.
6

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>

2 Comments

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.
It's going to override the document and rest of the js can't operate properly.
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.

5 Comments

This should demonstrate it. Just watch the result window when you hit Ctrl + P to print. jsfiddle.net/captray/69z2wepo/68966
what if you still need the document.getElementById('root')?
@FiddleFreak Not sure I understand your question? It's been three years since I posted this, so I might need more context.
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?
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.
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.

Comments

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://stackoverflow.com/a/30137174/3123109

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.

1 Comment

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);
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 />

1 Comment

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.
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();
        });
    }

3 Comments

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.
Sorry, but What was not understandble?
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. tc
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>
  );
}
`

Comments

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}

Comments

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;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.