Adding a header & footer on every print page in a React App

Simant Thapa Magar
readytowork, Inc.
Published in
10 min readAug 16, 2023

While creating a frontend application, we sometimes come across the need to print the view that is seen on the screen. On top of that we require to make some changes so that the printed document doesn’t look like a screenshot of screen but rather a view personalized for a hardcopy. In this article, we will look into one of those personalized changes involving the appearance of the header & footer on every page to be printed such that it looks like an official copy.

The trick to achieve this is pretty simple though. We just need to wrap the contents to be printed inside a table and include the header to be shown inside thead tag and footer inside tfoot tag. The approach we is based on CSS, JavaScript, and browser’s print functionality and requires no additional package. Therefore, it is not only applicable for a React application but also for other frontend javascript frameworks. However, the disadvantage is that it is highly dependent on the browser’s compatibility.

Now let’s look into implementation by first creating a React application.

npx create-react-app react-print

Next, create a component that takes the contents to be printed and wraps it with the header and footer. The component renders a button that triggers the print upon click and a table with contents for the header inside thead , contents to be printed inside tbody and contents for the footer inside tfoot. For demo purpose we will show the react logo and a text as header and a simple text as footer.

import logo from './../logo.svg';
const PrintComponent = ({children}) => {
const printAction = () => {
window.print()
}
return (<>
<button className={"print-preview-button"} onClick={printAction}>{"Print Preview"}</button>
<table className="print-component">
<thead>
<tr>
<th>
<img src={logo} height={"40px"} width={"40px"} alt="logo" />
<div>
{"Page Header"}
</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
{children}
</td>
</tr>
</tbody>
<tfoot className="table-footer">
<tr>
<td>
{"Page footer"}
</td>
</tr>
</tfoot>
</table>
</>
)
}

export default PrintComponent

The button will be visible initially and hidden for the print page while the content is hidden initially and will be visible for the print page only. Therefore, with this consideration in mind here’s a simple stylesheet for the application.

.print-preview-button {
@media print {
display: none;
}
}

.print-component {
display: none;
@media print {
display: table;
.table-footer > tr > td{
text-align: center;
background-color: grey;
color: white;
}
}
}

By simply importing the component inside the main App.js and passing necessary contents as children to the component, we can see the output after clicking the button

import './App.css';
import PrintComponent from './Components/Print';

function App() {
return (
<div>
<PrintComponent>
<div>
{`What is Lorem Ipsum?
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

Why do we use it?
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).


Where does it come from?
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.

The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.

Where can I get some?
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.`}
</div>
<div>
{`What is Lorem Ipsum?
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

Why do we use it?
It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).

Where does it come from?
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.

The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.

Where can I get some?
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.`}
</div>
</PrintComponent>
</div>
);
}

export default App;

On clicking the button, we can see that the header and footer are appearing on both pages as shown in the image below.

Page 1
Page 2

We have achieved showing header & footer on all print pages, however, there is a small concern. As we can see on the second page the footer is not at the bottom of the page but rather at the position where the content ends. For those whose requirement accepts this may not require further customization. However, for those who prefer the footer to always appear at the bottom of page then we need to apply some tricks discussed below.

The trick to achieve this is right in front of us. As shown in the image, the first page has a footer at the bottom of the page which means that if we set a suitable height for the component then the footer will appear at the bottom of the page. However, we have some issues doing so. First of all, what is that suitable height? Moreover, the content is set as display: none initially so its height is 0. We will deal with all these in following approach

  • Get the height that content would occupy
  • Set that height for print

DISCLAIMER: The following implementation has been done with reference to portrait print mode in A4. While the approach is the same, the numbers used will vary depending on preference.

For portrait mode in A4 size, a height of 1045 px would be ideal for 1 page. While this is an approximate value please try changing it if it doesn’t meet the need. This is the starting point of the calculation and will depend on this figure. Similarly a width of 720px is an ideal width for same configuration with default margins.

Now we will calculate the total height the content would occupy for given width. Before the print preview is displayed, we add a class to the print component that changes some of its style so that we can determine its height. The class has a style as follows

.temp-class-for-height {
// media not print so that it doesn't interfere with print style
@media not print {
// for A4 portrait
width: 720px;
visibility: hidden;
display: table;
}
}
const printElement = document.getElementById("print-component")
printElement.classList.add("temp-class-for-height")
const height = printElement.clientHeight

At this point the height we get has a single header and footer but on print page it will be present for each page. So, we need to determine the total number of pages required. As we have already set a standard for height of each print page, we can determine total as follows

const numberOfPage = Math.ceil(height / PAGE_HEIGHT)

Now if the number of pages is 1 then we don’t need to do any further calculation as the header & footer are already considered for one one-time appearance. However, for multiple occurrences, the height required can be calculated below

let requiredHeight = heightWithSingleHeader
if (numberOfPage > 1) {
const headerHeight = printElement.getElementsByTagName("thead")?.[0]?.clientHeight || 0
const footerHeight = printElement.getElementsByTagName("tfoot")?.[0]?.clientHeight || 0
requiredHeight -= (numberOfPage - 1) * (headerHeight + footerHeight)
}

Now we will simply set the height of the print component to the calculated height and trigger print

printElement.style.height = `${requiredHeight}px`
window.print()

By this time, we will see a print preview in the format desired. However, the manipulations that we did recently to determine height can be undone as they were temporary changes. So we will remove the added class and set the height to auto after the preview is shown such that it is somewhat similar to the initial layout.

printElement.classList.remove("temp-class-for-height")
printElement.style.height = `auto`

All these changes were carried out inside the function that handled print action so final changes would be as follow

const printAction = () => {
const PAGE_HEIGHT = 1045
const printElement = document.getElementById("print-component")
if (printElement) {
printElement.classList.add("temp-class-for-height")
const height = printElement.clientHeight
const numberOfPage = Math.ceil(height / PAGE_HEIGHT)
const heightWithSingleHeader = numberOfPage*PAGE_HEIGHT
let requiredHeight = heightWithSingleHeader
if (numberOfPage > 1) {
const headerHeight = printElement.getElementsByTagName("thead")?.[0]?.clientHeight
const footerHeight = printElement.getElementsByTagName("tfoot")?.[0]?.clientHeight
requiredHeight -= (numberOfPage - 1) * (headerHeight + footerHeight)
}
printElement.style.height = `${requiredHeight}px`
printElement.classList.remove("temp-class-for-height")
}
window.print()
if (printElement) {
printElement.style.height = `auto`
}
}

The final output of the second print page is shown as below with the footer at the bottom despite the content not being that long.

Hope this trick will be of some assistance for those looking for a solution in a similar case.

--

--