Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

download pdf on custom button click #975

Closed
pradeepn541 opened this issue Aug 13, 2020 · 23 comments · Fixed by #1902
Closed

download pdf on custom button click #975

pradeepn541 opened this issue Aug 13, 2020 · 23 comments · Fixed by #1902

Comments

@pradeepn541
Copy link

hi,

Is there any way i can download my pdf on my custom button click without using in built PdfDownloadLink ?

@vuki656
Copy link

vuki656 commented Aug 14, 2020

Also, need this.

Right now I'm passing the link to the button text. It's a little hacky.

It would be nice to be provided with a trigger that could be passed to onClick handlers.

@pradeepn541
Copy link
Author

that

Can you please explain how you are passing link to the button text ?

@vuki656
Copy link

vuki656 commented Aug 14, 2020

that

Can you please explain how you are passing the link to the button text?

Default button

<Button>Click Me</Button>

With PDF

<Button><PDFDownloadButton></Button

and then in the PDFDownloadLink component, you pass the text and the pdf


<PDFDownloadLink
    fileName={`fileName}
    document={document}
>
   Click Me
</PDFDownloadLink>

Note that this will generate the pdf when the page loads. If you don't want that you'll need to mess with loading and displaying the link based on if the button is clicked.

@JacobFischer
Copy link

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

@oowowaee
Copy link

Not sure if this is what you're looking for, but I was able to resolve the lazy load + custom button using a version of #736.

@pradeepn541
Copy link
Author

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

Thanks,
It worked.Cool 👍

Is there any chance we can put this block of execution into web worker ?

@muhammadhassan149
Copy link

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

Can you please send the related files, e.g document etc, I've to fetch the data in document from api

@JacobFischer
Copy link

Sure, here's a larger form file with the example code I posted above:

https://gist.github.com/JacobFischer/aecbd871cb2aae46993236f65797da5c

That should help show you how to fetch asynchronous data for your document

@PauloQuagliatto
Copy link

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

Hey, I've tried your code but there are some issues about passing the data. I am passing the data like (the visible notes are the notes that are shown given proper redux filter and store)
and like so I pass the props to your code:

export const LazyDownloadPDFButton = () => (
 <button
    onClick={async () => {
      const doc = <DocumentPdf {...props} />;

With this I want to pass the dynamic data directly to the pdf file, but it crashes saying this: Error: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead.
So, how would I pass this data? Saw your complete example but still can't see a way out.

@JacobFischer
Copy link

@PauloQuagliatto, according to this comment: https://gist.github.com/JacobFischer/aecbd871cb2aae46993236f65797da5c#gistcomment-3479589

Replace:

const asPdf = pdf({});

with:

const asPdf = pdf([]);

Both hacks work for me. Though perhaps how you are structuring your pdf is similar to that comment.

@PauloQuagliatto
Copy link

@PauloQuagliatto, according to this comment: https://gist.github.com/JacobFischer/aecbd871cb2aae46993236f65797da5c#gistcomment-3479589

Replace:

const asPdf = pdf({});

with:

const asPdf = pdf([]);

Both hacks work for me. Though perhaps how you are structuring your pdf is similar to that comment.

This actually worked! Many thanks!

@stonesthatwhisper
Copy link

stonesthatwhisper commented Oct 27, 2020

Update: never mind. Set image: { objectFit: 'contain' } with StyleSheet fixed it.

I have an <Image /> in the Document, and your solution scales that image in both directions differently, while PDFDownloadLink does not (but it is not lazy, hence I need a solution like yours). Do you know why pdf has this problem? How can I fix it?

@PauloQuagliatto, according to this comment: https://gist.github.com/JacobFischer/aecbd871cb2aae46993236f65797da5c#gistcomment-3479589

Replace:

const asPdf = pdf({});

with:

const asPdf = pdf([]);

Both hacks work for me. Though perhaps how you are structuring your pdf is similar to that comment.

@mohannadhanafi
Copy link

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

Thanks a lot, I faced the same problem and finally it worked 🥳

@diegomura
Copy link
Owner

@JacobFischer seems like a good solution. Thanks!

@theutkarshsoni
Copy link

@JacobFischer Thanks a lot. You saved my entire day. Really appreciate.

@WilliamPhilippe
Copy link

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

Thank you so much!!!

@adamduncan
Copy link
Contributor

adamduncan commented Jun 29, 2022

Would still +1 on adding an onClick to <PDFDownloadLink> API.

See PR: #1902

Edit: Currently working around this by using usePDF hook instead

@valentimcanejo
Copy link

I've accomplished this via some headaches that I'm trying to solve in #972. Here's an example of how to do a custom onClick handler on a button to lazy load the PDF:

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
  <button
    onClick={async () => {
      const props = await getProps();
      const doc = <DocumentPdf {...props} />;
      const asPdf = pdf({}); // {} is important, throws without an argument
      asPdf.updateContainer(doc);
      const blob = await asPdf.toBlob();
      saveAs(blob, 'document.pdf');
    }}
  >Download PDF</button>
);

Feel free to tweak to your needs. I slimmed my code down quite a bit for the example here. However, in a real code base I also track when it is async loading the pdf blob to disable multiple onClick events, and cache the generated blob to speed up subsequent download clicks.

Using <PDFDownloadLink> as pointed out generates the PDF when the page loads, which in my use case was not ideal and increased page loading times dramatically. I'd imagine many other developers do not need to generate the PDF until a user clicks on their button, as I did.

I hope this helps.

Man, thank you so much, you saved my code.

Did you created a loading behavior? I've put a setLoading(true) and a setLoading(false) inside the onClick, but it doesn't work correctly in a PDF with large data, how can i create a better loading?

@hmcd101
Copy link

hmcd101 commented Mar 21, 2023

import { pdf } from '@react-pdf/renderer';
import { saveAs } from 'file-saver';
import React from 'react';
import { DocumentPdf, getProps } from './document-pdf';

export const LazyDownloadPDFButton = () => (
<button
onClick={async () => {
const props = await getProps();
const doc = <DocumentPdf {...props} />;
const asPdf = pdf({}); // {} is important, throws without an argument
asPdf.updateContainer(doc);
const blob = await asPdf.toBlob();
saveAs(blob, 'document.pdf');
}}
Download PDF
);

Much appreciated! Worked well for me. I too added the state values to track PDF generation and disable the button. Worked well, though my PDF is not extremely large.

@Tint-d
Copy link

Tint-d commented Jul 2, 2023

hey how use it with rtk state?

@Chakkaphong9
Copy link

Update: never mind. Set image: { objectFit: 'contain' } with StyleSheet fixed it.

I have an <Image /> in the Document, and your solution scales that image in both directions differently, while PDFDownloadLink does not (but it is not lazy, hence I need a solution like yours). Do you know why pdf has this problem? How can I fix it?

@PauloQuagliatto, according to this comment: https://gist.github.com/JacobFischer/aecbd871cb2aae46993236f65797da5c#gistcomment-3479589
Replace:

const asPdf = pdf({});

with:

const asPdf = pdf([]);

Both hacks work for me. Though perhaps how you are structuring your pdf is similar to that comment.

this work for me.

@rbrahier17
Copy link

rbrahier17 commented Oct 5, 2023

If it can help, here is an example of a basic react button component accomplishing this lazy pdf downloading, with the react-pdf usePDF hook and react hooks:

import React, { useState, useEffect } from "react";
import { usePDF, Document, Page, Text } from "@react-pdf/renderer";

// Define the PDF document (you can also import it)
const myPdfDocument = (
  <Document>
    <Page>
      <Text>Hello PDF Document</Text>
    </Page>
  </Document>
);

/**
 * Triggers a download for a given URL by creating a hidden anchor element.
 * @param url - The URL of the file to be downloaded.
 * @param title - The title to be used for the downloaded file (default is 'document').
 */
function triggerDownload(url: string, title: string = "document") {
  const a = document.createElement("a");
  a.href = url;
  a.download = title + ".pdf";
  a.click();
}

export function DownloadPdfButton() {
  const [pdfInstance, updatePdfInstance] = usePDF({}); // Instantiate an empty PDF instance using the usePDF hook
  const [isDownloading, setIsDownloading] = useState(false);

  function initiateDownload() {
    setIsDownloading(true);
    updatePdfInstance(myPdfDocument); // Update the PDF instance with your document when the download button is clicked
  }

  // Trigger the PDF download when the button is clicked and the PDF instance URL is available
  useEffect(() => {
    if (isDownloading && pdfInstance.url) {
      triggerDownload(pdfInstance.url)
      setIsDownloading(false);
    }
  }, [isDownloading, pdfInstance.url]);


  return (
    <button onClick={initiateDownload} disabled={isDownloading}>
      {isDownloading ? "Loading..." : "Download PDF"}
    </button>
  );
}

If you have any remarks or suggestions about that I'll be happy to read them, hope this helps !

@meshramsaroj
Copy link

If it can help, here is an example of a basic react button component accomplishing this lazy pdf downloading, with the react-pdf usePDF hook and react hooks:

import React, { useState, useEffect } from "react";
import { usePDF, Document, Page, Text } from "@react-pdf/renderer";

// Define the PDF document (you can also import it)
const myPdfDocument = (
  <Document>
    <Page>
      <Text>Hello PDF Document</Text>
    </Page>
  </Document>
);

/**
 * Triggers a download for a given URL by creating a hidden anchor element.
 * @param url - The URL of the file to be downloaded.
 * @param title - The title to be used for the downloaded file (default is 'document').
 */
function triggerDownload(url: string, title: string = "document") {
  const a = document.createElement("a");
  a.href = url;
  a.download = title + ".pdf";
  a.click();
}

export function DownloadPdfButton() {
  const [pdfInstance, updatePdfInstance] = usePDF({}); // Instantiate an empty PDF instance using the usePDF hook
  const [isDownloading, setIsDownloading] = useState(false);

  function initiateDownload() {
    setIsDownloading(true);
    updatePdfInstance(myPdfDocument); // Update the PDF instance with your document when the download button is clicked
  }

  // Trigger the PDF download when the button is clicked and the PDF instance URL is available
  useEffect(() => {
    if (isDownloading && pdfInstance.url) {
      triggerDownload(pdfInstance.url)
      setIsDownloading(false);
    }
  }, [isDownloading, pdfInstance.url]);


  return (
    <button onClick={initiateDownload} disabled={isDownloading}>
      {isDownloading ? "Loading..." : "Download PDF"}
    </button>
  );
}

If you have any remarks or suggestions about that I'll be happy to read them, hope this helps !

This solution really helped me. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.