添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Generating a PDF from a webpage is becoming essential in web development, especially in a report-driven application. It is often used in scenarios ranging from creating an invoice in an e-commerce application, printing transactional reports, generating reports, etc. In this article, we will explore how to create an application that generates, prints, downloads, and shares a PDF with ReactJS and React-PDF .

You can also check out eduproject , explaining the design and implementation of custom PDF using React and React-pdf.

Together, we will go through step by step procedure I use to design an e-commerce invoice with the react-pdf package.

  • Next, let’s create these files Invoice.js and PdfCard.js , we will be adding our code to them as we go along in this guide.
  • If using a Mac OS, let’s run the below command to create these files

    touch src/Invoice.js touch src/PdfCard.js

    If using a Windows OS, let’s run the below command to create the files, make sure to run the command one after the other.

    cd.> src/Invoice.js

    Basically, we will be working with the App.js file which is our root file, the Invoice.js, and PdfCard.js .

    The react-pdf package will be used to design an e-commerce invoice sample. Before diving in properly, let’s work through the building block of the react-pdf package.

    PDFViewer : This component will display the PDF documents within the web browser. Document : This component represents the PDF document and is the parent component to other components. Page : As the name implies, it represents a single page within a PDF document. View : This is the core component for designing the UI. Text : This component is used for displaying text Image : This component displays images within the document. Support image formats such as JPG, PNG, GIF, etc. PDFDownloadLink : This component creates a download link for a PDF document generated by the React-pdf library. BlobProvider : This component generates a PDF document and provides it as a Blob object that can be used to display or download the PDF document.

    With this out of the way, let's create our PDF.

    import React from 'react'
        import { Image, Text, View, Page, Document, StyleSheet } from '@react-pdf/renderer';
        const Invoice = () => {
        const reciept_data = {
        // update reciept_data here
        const styles = StyleSheet.create({
        // update Invoice styles here 
        const InvoiceTitle = () => (
        // update InvoiceTitle component here 
        const Address = () => (
        // update Address component here
        const UserAddress = () => (
        // update UserAddress component here
        const TableHead = () => (
        // update TableHead component here
        const TableBody = () => (
        // update TableBody component here
        const TableTotal = () => (
        // update TableTotal component here
          return (
                <Document>
                    <Page size="A4" style={styles.page}>
                        <InvoiceTitle  />
                        <Address/>
                        <UserAddress/>
                        <TableHead/>
                        <TableBody/>
                        <TableTotal/>
                    </Page>
                </Document>
        export default Invoice
        Enter fullscreen mode
        Exit fullscreen mode
    

    The above image shows the section and position of each component in the Invoice design sample.

    Open the App.js file and replace the existing code with code as below:

     import { PDFViewer } from "@react-pdf/renderer";
        import Invoice from "./Invoice";
        function App() {
          return (
              <PDFViewer width="1000" height="650" className="app" >
                <Invoice />
              </PDFViewer>
            </div>
        export default App;
        Enter fullscreen mode
        Exit fullscreen mode
    
    const InvoiceTitle = () => (
                <View style={styles.titleContainer}>
                    <View style={styles.spaceBetween}>
                        <Image style={styles.logo} src={logo} />
                        <Text style={styles.reportTitle}>Xpress Enterprises</Text>
                    </View>
                </View>
        Enter fullscreen mode
        Exit fullscreen mode
    
    const Address = () => (
                <View style={styles.titleContainer}>
                    <View style={styles.spaceBetween}>
                            <Text style={styles.invoice}>Invoice </Text>
                            <Text style={styles.invoiceNumber}>Invoice number: {reciept_data.invoice_no} </Text>
                        </View>
                            <Text style={styles.addressTitle}>7, Ademola Odede, </Text>
                            <Text style={styles.addressTitle}>Ikeja,</Text>
                            <Text style={styles.addressTitle}>Lagos, Nigeria.</Text>
                        </View>
                    </View>
                </View>
        Enter fullscreen mode
        Exit fullscreen mode
    
    const UserAddress = () => (
                <View style={styles.titleContainer}>
                    <View style={styles.spaceBetween}>
                        <View style={{maxWidth : 200}}>
                            <Text style={styles.addressTitle}>Bill to </Text>
                            <Text style={styles.address}>
                                {reciept_data.address}
                            </Text>
                        </View>
                        <Text style={styles.addressTitle}>{reciept_data.date}</Text>
                    </View>
                </View>
        Enter fullscreen mode
        Exit fullscreen mode
    
     const TableHead = () => (
                <View style={{ width:'100%', flexDirection :'row', marginTop:10}}>
                    <View style={[styles.theader, styles.theader2]}>
                        <Text >Items</Text>   
                    </View>
                    <View style={styles.theader}>
                        <Text>Price</Text>   
                    </View>
                    <View style={styles.theader}>
                        <Text>Qty</Text>   
                    </View>
                    <View style={styles.theader}>
                        <Text>Amount</Text>   
                    </View>
                </View>
        Enter fullscreen mode
        Exit fullscreen mode
    
    const TableBody = () => (
               reciept_data.items.map((receipt)=>(
                <Fragment key={receipt.id}>
                    <View style={{ width:'100%', flexDirection :'row'}}>
                        <View style={[styles.tbody, styles.tbody2]}>
                            <Text >{receipt.desc}</Text>   
                        </View>
                        <View style={styles.tbody}>
                            <Text>{receipt.price} </Text>   
                        </View>
                        <View style={styles.tbody}>
                            <Text>{receipt.qty}</Text>   
                        </View>
                        <View style={styles.tbody}>
                            <Text>{(receipt.price * receipt.qty).toFixed(2)}</Text>   
                        </View>
                    </View>
                </Fragment>
        Enter fullscreen mode
        Exit fullscreen mode
    
    const TableTotal = () => (
                <View style={{ width:'100%', flexDirection :'row'}}>
                    <View style={styles.total}>
                        <Text></Text>   
                    </View>
                    <View style={styles.total}>
                        <Text> </Text>   
                    </View>
                    <View style={styles.tbody}>
                        <Text>Total</Text>   
                    </View>
                    <View style={styles.tbody}>
                            {reciept_data.items.reduce((sum, item)=> sum + (item.price * item.qty), 0)}
                        </Text>  
                    </View>
                </View>
        Enter fullscreen mode
        Exit fullscreen mode
    

    Now we have a PDF Invoice file that can be downloaded, printed, and shared via email.
    In a real-world scenario, it is likely we have an Invoice or list of invoices requiring actions such as downloading, printing, and sharing via email. Using some custom hooks from React-PDF, we can implement these features. Let's update our design to accommodate these action buttons (download, print, and share).

    Let's create a new file PdfCard.js update as below:

    import React from 'react'
        import {CgFileDocument} from 'react-icons/cg'
        import {HiOutlineDownload, HiOutlinePrinter} from 'react-icons/hi'
        import {FiShare2} from 'react-icons/fi'
        const PdfCard = ({title}) => {
            const styles = {
                container : {  width:'220px',  borderRadius : '5px',  padding:'15px 12px',  display:'flex',  flexDirection:'column',  gap:'15px',  boxShadow: "0 3px 10px rgb(0 0 0 / 0.2)"},
                flex : { width:'100%', display:'flex', gap:'5px', alignItems:'center' },
                bold : { fontSize:'13px', fontWeight: 600},
                thin : {  fontSize:'11px',  color:'#6f6f6f',  fontWeight: 500 },
                btn:{ borderRadius : '3px', border:'1px solid gray', display : 'flex', alignItems :'center', gap:'2px', padding : '3px', fontSize:'11px', color:'#4f4f4f', fontWeight: 600, cursor:'pointer', userSelect:'none'}
          return (
            <div style={styles.container}>
                <div style={styles.flex}>
                    <CgFileDocument color='#90e0ef' size={20}/>
                    <span style={styles.bold}>{title}</span>
                </div>
                <div style={styles.thin}>
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus eligendi reiciendis fuga doloremque
                </div>
                <div style={{...styles.flex, ...{justifyContent:'space-between'}}}>
                    <div style={styles.btn}>
                        <HiOutlineDownload size={14}/>
                        <span>Download</span>
                    </div>
                    <div style={styles.btn}>
                        <HiOutlinePrinter size={14}/>
                        <span>Print</span>
                    </div>
                    <div style={styles.btn}>
                        <FiShare2 size={14}/>
                        <span>Share</span>
                    </div>
                </div>
            </div>
        export default PdfCard
        Enter fullscreen mode
        Exit fullscreen mode
        function App() {
          const cards = {  maxWidth: "1200px", margin: "0 auto", display: "grid", gap: "1rem", padding : '20px', gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))"}
          return (
              <h2 style={{textAlign:'center'}}>List of invoices</h2>
              <div style={cards}>
                <PdfCard title="Oasic ltd Invoice"/>
                <PdfCard title="Libra ltd Invoice"/>
                <PdfCard title="Xpress ltd Invoice"/>
                <PdfCard title="Cardic ltd Invoice"/>
              </div>
            </div>
        export default App;
        Enter fullscreen mode
        Exit fullscreen mode
    

    To download PDF we will be using a custom hook PDFDownloadLink from React-PDF. This component creates a download link for a PDF document generated by the react-pdf library.
    Let's update our PdfCard.js with the PDFDownloadLink as below:

    <PDFDownloadLink document={<Invoice />} fileName='invoice.pdf'>
           <div style={styles.btn}>
              <HiOutlineDownload size={14}/>
              <span>Download</span>
           </div>
         </PDFDownloadLink>
        Enter fullscreen mode
        Exit fullscreen mode
    

    To print PDFs, we will use a custom hook BlobProvider from React-PDF. This component generates a PDF document and provides it as a Blob object that can be used to display or download the PDF document.

    Let's update our PdfCard.js with the BlobProvider as below:

    <BlobProvider document={<Invoice />}>
          {({ url, blob }) => (
             <a href={url} target="_blank" style={styles.btn}>
                <HiOutlinePrinter size={14}/>
                   <span>Print</span>
         </BlobProvider>
        Enter fullscreen mode
        Exit fullscreen mode
    

    To share PDFs via email, we will use a custom hook, BlobProvider from React-PDF. This implementation will download a PDF file to the computer's local disk and open a mailbox for us to attach the downloaded file.

    To see this in action, let's update our PdfCard.js as below:

    import { saveAs } from "file-saver";
        const handleShare = async (blob) => {
                await saveAs(blob, `invoice.pdf`);
                window.location.href = `mailto:?subject=${encodeURIComponent(`Invoice`)}&body=${encodeURIComponent(`Kindly find attached invoice`)}`;
         <BlobProvider document={<Invoice />}>
           {({ url, blob }) => (
             <div style={styles.btn} onClick={() => handleShare(url, blob)} >
                <FiShare2 size={14} />
                <span>Share</span>
             </div>
        </BlobProvider>
    

    In this article, we built an application that generates, prints, downloads, and shares a PDF with ReactJS and React-PDF.

    To learn more about React-PDF, check out its official documentation.

    If this article is helpful, consider liking, sharing, and following me; connect with me on Twitter for more articles like this.

    Happy coding.

    Built on Forem — the open source software that powers DEV and other inclusive communities.

    Made with love and Ruby on Rails. DEV Community © 2016 - 2024.