Printing in React.js

Printing in React.js

In this article we shall discuss the following topics and make it as clear as we can:

  1. Prerequisites
  2. Introduction to printing in React.js
  3. Designing your print template
  4. How to print in portrait and Landscape (Implementation)
  5. Gotchas, tips, and advises
  6. Links (Documentations and GitHub repo)


Prerequisites

To get the most out of this article you are expected to know at least the basics of the following technologies:

  • React.js
  • How to set up a React.js project
  • How to install npm packages and run Reac.js projects


Am using Vite but you can use Create React App, Gatsby, or whatever tool you are comfortable using React.js with. Below is my project structure:

├── node_modules/

├── public/

├── src/

├── assets/

├── components/

├── Portrait.jsx

├── Landscape.jsx

├── App.css

├── index.css

├── index.jsx

├── index.html

├── .gitignore

├── package.json

├── README.md


Introduction to printing in React.js

If you have been building single-page applications ( SPAs) with React.js you will most likely like want to print something at some point. Be it an invoice, transaction history, certificates, etc.

The npm package we will be using in this article is react-to-print. Hence you can go ahead and run npm i react-to-print in your project.


Designing your print template

Designing your print template is nothing out of this world. It is just a normal React Component with your styling. I will be using tailwind css for my design but you can do whatever works for you.

Congratulations on making it this far and thank you for your patience. It has been a lot. Let's hop into the implementation.


How to print in portrait and Landscape (Implementation)


app.jsx ( Scroll below for the copyable code. Am using screenshots because the LinkedIn code/syntax highlighter is just that...)

Article content
Article content
Article content
Article content
Article content
Article content
Article content
import React, { useRef } from "react";

/**

 * First we import the useReactToPrint hook from react-to-print

 * This hook is how we configure our print Document/Template

 */

import { useReactToPrint } from "react-to-print";

import Portrait from "./components/Portrait";

import Landscape from "./components/Landscape";

const App = () => {
  /**

   * We are using refs to target our prints/templates

   *

   * What we currently have is two refs for portrait and landscape printing

   * We are doing this because what we want is when the user click on either portrait or landscape to make their wish come through

   *

   */

  const portraitComponentRef = useRef();

  const landscapeComponentRef = useRef();

  /**

   * The useReactToPrint hook comes equipped with alot of properities you can pass to it.

   * However, we are just going to explore 2 for this article.

   * content -  Is the ref value to the component we are printing. In this case Portrait.jsx and landscape.jsx

   * documentTitle - Is the default title of the document incase you decide to save the print instead of printing.

   */

  const handlePrintForPortrait = useReactToPrint({
    content: () => portraitComponentRef.current,

    documentTitle: "portrait-print",
  });

  const handlePrintForLandscape = useReactToPrint({
    content: () => landscapeComponentRef.current,

    documentTitle: "landscape-print",
  });

  return (
    /**

     * For our styling we are using tailwind css. Is just two cards with the cursor-pointer and a full-height screen and a gray background with some text labels.

     */

    <div className="flex flex-col gap-3 items-center justify-center h-screen">
      <h3 className="text-3xl mb-3">Choose your printing?</h3>

      <div className="bg-gray-50  flex items-stretch gap-3">
        <div
          /**

           * First let me help you with the differnce between onClick={() => handlePrintForPortrait()} and onClick={handlePrintForPortrait}

           * I asked ChatGPT for help and this is what it has to say:

           * onClick={handlePrintForPortrait} directly passes a reference to the handlePrintForPortrait function, which is invoked only when the click event occurs.

           * onClick={() => handlePrintForPortrait()} wraps the handlePrintForPortrait function in an arrow function, immediately executing it. This approach is useful when you need to pass arguments or manipulate the context (this).

           * The first approach is concise and often more performant, while the second provides more control over function invocation.

           *

           * This also applied to handlePrintForLandscape

           */

          onClick={handlePrintForPortrait}
          className="bg-white shadow-xl rounded-2xl w-96 h-96 cursor-pointer hover:shadow-2xl flex items-center justify-center"
        >
          <h3 className="text-2xl font-bold">Portrait</h3>
        </div>

        <div
          onClick={handlePrintForLandscape}
          className="bg-white shadow-xl rounded-2xl w-96 h-96 cursor-pointer hover:shadow-2xl flex items-center justify-center"
        >
          <h3 className="text-2xl font-bold">Landscape</h3>
        </div>
      </div>

      {/* Our Component refs */}

      {/**

       * We are wrapping our prints/component refs with a div of className hidden

       * Tailwind 'hidden' class is  the same as display: none; in CSS

       *

       * Why are we doing this?

       * If we do not do this the component refs or prints will appear in our DOM and we definitely do not want that.

       *

       */}

      <div className="hidden">
        {/* Portraits print component starts here */}

        <div ref={portraitComponentRef}>
          <Portrait />

          {/* This part is optional but it is required if you want to change the orientation of your page */}

          <style type="text/css" media="print">
            {
              "\
  @page { size: portrait; }\
"
            }
          </style>
        </div>

        {/* Portraits print component ends here */}

        {/* 

////////////////////////////////////////////////////////////////////////////////////

*/}

        {/* Landscape print component starts here */}

        <div ref={landscapeComponentRef}>
          <Landscape />

          {/* This part is optional but it is required if you want to change the orientation of your page */}

          <style type="text/css" media="print">
            {
              "\
  @page { size: landscape; }\
"
            }
          </style>
        </div>

        {/* Landscape print component ends here */}
      </div>
    </div>
  );
};

export default App;
        

Code Explanation

The code is well-commented and I like to think it is self-explanatory. For your convenience check it out on GitHub or you can comment on the article for any clarifications needed.


Gotchas, tips, and advises

  • If you've created a component that is intended only for printing and should not render in the parent component, wrap that component in a div with style set to { display: "none" }, like so:

<div style={{ display: "none" }}>
 <div ref={componentRef}>
  <ComponentToPrint />
 </div>
</div>        

  • While you should be able to place these styles anywhere, sometimes the browser doesn't always pick them up. To force orientation of the page you can include the following in the component being printed as we did in the example above

<style type="text/css" media="print">{"\
  @page {\ size: landscape;\ }\
"}</style>        

  • documentTitle will not work if react-to-print is running within an iframe. If react-to-print is running within an iframe.
  • When printing, only styles that directly target the printed nodes will be applied, since the parent nodes will not exist in the DOM used for the print. For example, in the code below, if the <p> tag is the root of the ComponentToPrint then the red styling will not be applied. Be sure to target all printed content directly and not from unprinted parents.

<div className="parent">
  <p>Hello</p>
</div>        
div.parent p { color:red; }        



Links (Documentations and GitHub repo)



To view or add a comment, sign in

More articles by Alieu Keita

Others also viewed

Explore content categories