React-pdf: Generate PDF on the fly

Created on 30 May 2017  ·  61Comments  ·  Source: diegomura/react-pdf

Thanks for a great library I was playing around with something similar recently.

This is what I am trying to solve currently and wondering if it is/will be possible with react-pdf:
I need to be able to generate loads of different PDFs in our platform and would love to do that fully on the frontend. The user clicks download button which allows him to download a generated PDF by something like react-pdf?

new feature

Most helpful comment

Yes! The code is here: https://github.com/react-pdf/site

The REPL works completely on the front-end. There is no client-server communications going on in the REPL, at least for the PDF generation. It's just using react-pdf and react-pdf DOM bindings, creates the blob client-side and makes it possible to download.

All 61 comments

Hi @knowbody !
This is something that will definitely be possible with react-pdf, specially since we are using pdfkit above, which has this function already built-in inside.
Unfortunately, it is not possible _yet_. @jbovenschen is working on making the pdfkit bundle easier to use in the browser, while I'm making progress with the primitives itself. After that, these other features will be supported 😄

@diegomura thanks for the update! Any ETA on it? Also I might be able to spare a few evenings and help out.

hard to tell honestly. not less than 2 months, but also depends on how much help we can get and of course, our free time 😄 . we need to figure out those pdfkit issues, and also we are having some (partially solved) trouble with yoga and flexbox. imagery is also partially done, but we still need support for a bunch of other css properties such as fonts, tranforms, weights, color, etc.

contributions are very welcome! would be awesome if you can help us out with this features. thanks!

Currently my main target is to make pdfkit usefull for our usecase think it will take some time before it is integrated perfectly to have the best DX on our side.

@knowbody If you need it soon I can give you some pointers how I did solve this before with pdfmake if your interested?

was there any work done on this so far?

@knowbody I'm spending the free time I have currently to figure out a better way to bundle pdfkit so it works easy in the browser, also this would mean it will include less node internals bundled.

Don't have that much time to do so, sorry about that.

@jbovenschen no, not at all. I'm just curious if someone is working on it so I do not double up the work. Trying to find some time and hopefully I can take a look into it very soon

In case you are still interested: I recently created a PR (which is merged #153) so you can stream a pdf directly from a node.js service (your frontend could do a request). I intent to make a service which automates this open source asap.

If you need it to be completely done from the browser, I dont think that is possible (yet)

Friendly ping to see where you got to with 'on the fly' pdf generation and display. Im not running on a node environment, so being able to generate solely on the front end would be awesomely awesome :)

Hi @haxxxton!
It’s currently possible both displaying and creating pdf on they fly. React-pdf REPL is a good example of how this can work: http://react-pdf.diegomura.com/repl
There still might be some work to do in order to make it accessible though a nice and clean api, but it’s not that much code you have to write to have something like the REPL running on your project

@diegomura
ah awesome!
Is the code for the repl site available anywhere? i cant seem to find it in your repos

I wanted to clarify, that i was asking about a purely front end solution as im not on a node environment. It appears from your example site that it sends some kind of request to the backend that returns the pdf blob to be displayed. Im assuming there is a node server behind that it returning the pdf data from this request no? I am very possibly misunderstanding works though :)

Yes! The code is here: https://github.com/react-pdf/site

The REPL works completely on the front-end. There is no client-server communications going on in the REPL, at least for the PDF generation. It's just using react-pdf and react-pdf DOM bindings, creates the blob client-side and makes it possible to download.

@diegomura
so amazing!

one last question: does it require React 16.x? or can i still run it on React 15.x?

Good question.
React 16 is required, because react-pdf relays on the fiber reconcilier to work. I never tried it with React 15, so I encourage you to do it and see, but based on what I know and imagine, it won’t work unfortunately.

Hi, is it possible generate PDF without implementation of REPL and PDF preview?

It is currently not possible without the preview, what we can de is exposing a render prop which passes the PDF blob so you can do with it whatever you want.

Hi guys, thanks for this great project.

I setup the preview easily enough but can't get the "file download" functionality working (pure DOM project btw). Any examples I can follow along?

Any help for this would be greatly appreciated :)

Hey @rpedroni !
Yes. You can see how our landing page works in here: https://github.com/react-pdf/site

Hi @diegomura ! Thanks a lot for the quick response. I went with the "quick" html2canvas + jsPDF route to generate the pdf (had a short deadline, was a bit easier for me) but I really appreciate this project :)

Hi @diegomura, the library and the idea is amazing, thanks for sharing it with us.
I was checking the REPL and I could see some dependencies and versions (for example @react-pdf/react-df) that haven't been relesed yet. Do you think the 'on the fly' feature would work with the current version?
"@react-pdf/core": "^0.7.6", "@react-pdf/dom": "^0.7.6",

Hi everyone! Does anybody solved problem with saving file, I need help((

@diegomura Is it possible to explain a little bit why in the REPL site, the examples are in .txt files? I'm trying to understand a way to create the document as a standard React component using your library in a .jsx file with content based on state that I then display in the PDFViewer. Feeling like I might be missing something obvious to get this working?

@kylezinter the only reason why the examples are in .txt files is because they are loaded as strings to insert them in the code part of the REPL by using raw-loader. They can perfectly be js files though, it doesn't matter. Sorry that confused you when trying to use the lib, but they have nothing to do with the project itself, but rather on how the REPL is builded.

For what you ask, the README instructions should be sufficient. However, there are people with issues trying to implement live reloading. Is that the problem you're having? Sorry but I didn't get what's your issue based on your comment

@diegomura Thank you for the response. I'm creating a component that is the PDF document, I pass some props to it and it generates the document using your library. Then trying to put it into the react-pdf project's document viewer as you were doing on the Repl site but that project seems to not like passing it the direct component as the file.

I ran into that as well. input.document is null in the pdf().toBlob() function.

I can recreate that by creating a js component from page-wrap.txt and passing it directly to PDFViewer.

My use case was to supply a download button only. Here's my workaround.
https://gist.github.com/trbngr/42b6144db1c03f88879e831db98912bc

The pieces that enabled everything for me was grabbing the iframe.src

https://gist.github.com/trbngr/42b6144db1c03f88879e831db98912bc#file-pdfrenderer-js-L71
and
https://gist.github.com/trbngr/42b6144db1c03f88879e831db98912bc#file-pdfrenderer-js-L21

@kylezinter Not sure. That PDFViewer component is specific for the REPL, so you issues can have root on many things. Please check how the code is transpiled to see if something there gives you a hint. I plan to build a viewer over the road, but as it is right now that component is not “ready to use” from out of the box

Thanks @trbngr for providing a working solution! This is now on the top of my priority list, since many people are requiring it. However, I didn’t come with a nice API of how this can work. Basically: generating documents without rendering them on screen, and being able to download them.

I would like to hear your thoughts on this subject!
How would you expect react-pdf to solve this issue for you? (Code wise)

Honestly? Just give me a url argument in document.onRender ;)

I’m not giving you an url, but the blob itself already (latest version) 😄. You can transform that to URL easily.

However, I do think we need to provide a way of not having to show the document in the page. Maybe just a “shallow” prop would be enough

Ok. Pass the blob. 😍

So... I added this new concept of shallow rendering (maybe a better name?)
Basically enables you to generate a PDF document without actually showing it on the page (that's optional though), and by some simple bindings have a "download" button.

How it works:

class App extends React.Component {
  state = { url: null }

  onRender = ({ blob }) => {
    this.setState({ url: URL.createObjectURL(blob) })
  }

  render () {
    return (
      <div>
        <Document shallow onRender={this.onRender}>
          <Page size="A4" wrap>
            <Text>I was created on the fly!</Text>
          </Page>
        </Document>
        <a href={this.state.url}  download={`document.pdf`}> 
          Download
        </a>
      </div>
    )
  }
}

This enables a couple of things:

  • As I said, render a document without showing it on the page (or showing it, without the shallow prop)
  • Toggle easily between showing/not showing PDF
  • Create a download button and being able to pass a file name (even changing filename based on props or state)
  • Link directly to PDF document, removing the download prop from <a />
  • Open the document in another tab adding target="_blank" (be careful with AdBlocker)
  • Change document content on the fly based on props and state (try adding an interval changing the state and using it inside the doc. every time you click on download you download a different version)

Thoughts?? Do you see any use case that this might not be covering?

That is perfect, dude! Thanks a lot.

Is this published yet?

Not yet! Probably releasing a new version tomorrow. But wanted to have some feedback first. More comments are appreciated!

If you publish this today, I get to delete code tomorrow. It’s my favorite thing to do. 😋

Thank you very much. I couldn't help but to build it locally and play with it. The only issue I ran into was that (presumably) the size of the PDF was causing any state changes on the parent component to lag. Use of shouldComponentUpdate() cleared it up however.

This looks great! @diegomura

In this case you might want to use an inner object for the state of the pdf

this.state = {
  pdf: {
    data: undefined
  }
};
onRender={(state => this.setState({pdf: {data: state}}))};

That allows React to compare the states using === which is an reference check on objects (which this will fail on update, causing a render). The alternative is comparing a very long data URL string which is way more intensive.


You can also sidestep react here, and do something like this

this.state = { renderCount: 0 };
this.data = undefined;
onRender={state => {
    renderCount = this.state.renderCount + 1;
    this.data = state;
    this.setState({renderCount});
}}

That will keep the blob outside the state, but triggers a state change, so in the render you will get the newest this.data

Copying my comment on the PR here in case people on this thread have opinions/comments about my API proposal.

My thought is that this API is more congruent with the direction it seems like most libraries and still provides flexibilities for onRender hooks and other things but leaves the actual rendering logic open for clients to handle explicitly.


Hi @diegomura, excited to see you starting on this feature; I was actually about to start an API proposal, but you beat me to it here! I'll sketch it here instead to hear and could work on a POC if you're onboard.

How about using render props to pass the blob to children who can decide what to do with it? Something like (untested, very-pseudo code 😄 )

import React from "react"
import {BlobProvider, Document} from "@react-pdf/renderer"

const MyPdf = () => (
  <Document>
    ...
  </Document>
)

const MyFancyDownloadLink = () => {
  return (
      <BlobProvider document={<MyPdf />}>
        {({ blob }) => {
          const url = URL.createObjectURL(blob)
          return <a src={url}>Download</a>
        }}
      </BlobProvider>
  )
}

Or even

import { DownloadLink } from "@react-pdf/renderer"

const MyFancyDownloadLink = () => {
  return (
      <BlobProvider document={<MyPdf />}>
        {({ blob }) => <DownloadLink blob={blob} />}
      </BlobProvider>
  )
}

The shallow functionality could be implemented with this pattern with something like

import { IFrameViewer } from "@react-pdf/renderer"

const PdfModal = ({isOpen}) => {
  return (
      <BlobProvider document={<MyPdf />}>
        {({ blob }) => isOpen ? <IFrameViewer blob={blob}/> : null}
      </BlobProvider>
  )
}

Essentially, the BlobProvider would handle the actual PDF rendering, while the Document handles building the actual PDF document. As I work with the library, I typically have that idea in my head but then get surprised when the Document is also the iframe once actually rendered. In my mind, this is the Document doing more than one thing, which I believe may have caused some challenges with the API. If we make more explicit and SRP components, it may make things easier to use.

I'd love to hear your thoughts and apologies if this is on a completely different track from the project, I am likely missing lots of context 🙈

ping #236

And my second comment from the PR showing the same component @diegomura showed


import { DownloadLink, IFrameViewer, BlobProvider } from "@react-pdf/renderer"

const App = () => (
  <BlobProvider>
    {({ blob }) => (
      <div>
        <IFrameViewer blob={blob} />
        <DownloadLink blob={blob} title="document.pdf" />
      </div>
    )}
  </BlobProvider>
)

And one more iteration to have the library handle more of the url generation logic (i.e. URL.createObjectUrl) 😄

const MyFancyDownloadLink = () => {
  return (
      <BlobProvider document={<MyPdf />}>
        {({ blob, blobObjectUrl }) => <a src={blobObjectUrl}>Download</a>}
      </BlobProvider>
  )
}

I'm glad this is going in a good direction! Sorry I couldn't reply or publish early 😄

Thanks @bkoltai for your API proposal! I do think you have some good ideas with it, and I do see why Document rendering an iframe can be confusing. I think there are two main reasons why is that: keeping the same API of the lib both in server and web, and making Document the main root element which I think it makes sense from a declarative point of view.

I was eventually thinking of building a PDF viewer (not iframe. something similar of the one in the REPL, fully customizable), so the latter point will probably change, and an approach like yours will eventually be implemented (having an iframe viewer, and a more custom viewer. or maybe having the iframe by default. it's unclear)

It's odd to see BlobProvider as the root of the PDF element structure. From my point of view, the regular user shouldn't be even aware of the blob object (or what it is), and have the option to get it if he really needs it (from the onRender callback). Why not having the viewers be root element instead?

const MyDocument = () => {
  return (
      <IFrameViewer someProps>
        <Document>
          <Page>
            // whatever
          </Page>
        </Document>
      </IFrameViewer>
  )
}

I do feel this is something we can eventually implement, specially considering the existence of more than 1 viewer. BlobProvider can be part of a more extended API but I wouldn't make it the defacto solution.

About DownloadLink, why do you think would be better to include it as part of the lib if it's just a <a/> tag. For me it feels that we are hiding some complex logic while it's just a very simple solution.

I would love to hear your thoughts (and everyone else's)!

Heyo, hope you don't mind my opinion. As a preface: I'm not even using react-pdf yet, I'm just watching this issue because this is the base functionality I require before further digging into it (and I have so much on my to-do-list before "pdf creation" in my current project that I haven't come to the point of trying to hack a workaround together ;) ).

But yeah, here's some opinion ^^

  • I would not use the term "shallow". This might confuse people doing unit tests with shallow rendering.

  • On "direct viewer vs render props": why not both? There could be an underlying BlobProvider for complex usages and some more simple components without render props that use it internally. So advanced use cases could use the render prop, but simple use cases would stay simple.
    Here's my idea:

// assuming a "dummy" document-to-blob function
const documentToBlob = () => "something";
// and a "dummy" BlobViewer component
const BlobViewer = ({ blob }) => <div>{String(blob)}</div>;

// offer a BlobProvider component with a render prop
const BlobProvider = ({ document, children: render }) =>
  render(documentToBlob(document));

// most users will use the DocumentViewer Component like this:
/*
<DocumentViewer>
  <Document>
    <Page>
      foo
    </Page>
  </Document>
</DocumentViewer>
*/
const DocumentViewer = ({ children: document }) => (
  <BlobProvider document={document}>
    {blob => <BlobViewer blob={blob} />}
  </BlobProvider>
);
// some users might only need a download link, they can use this Component:
/*
<DocumentDownloadLink
  document={
  <Document>
    <Page>
      foo
    </Page>
  </Document>
  }
>
  <DownloadIconComponent> Download
</DocumentDownloadLink>

*/
const DocumentDownloadLink = ({ document, children }) => (
  <BlobProvider document={document}>
    {blob => <a href={URL.createObjectURL(blob)}>{children}</a>}
  </BlobProvider>
);

// and again, others might just like to create their own, more complicated component
// using BlobProvider directly
const CustomDocumentViewerWithLink = ({ children: document, text }) => (
  <BlobProvider document={document}>
    {blob => (
      <React.Fragment>
        <BlobViewer blob={blob} />
        <a href={URL.createObjectURL(blob)}>{text}</a>
      </React.Fragment>
    )}
  </BlobProvider>
);

(reading this again, I may have just repeated most of @bkoltai's suggestion. my point is: offering a RenderProp interface doesn't mean you can't also offer some simple components to end users that might not need the flexibility of a render prop)

  • as for the blobObjectUrl argument suggested by @bkoltai above: that would require converting the blob to an url at every render, no matter if the consumer actualy uses it, so I wouldn't do that.

Awesome, thanks for your response. My thoughts below:

keeping the same API of the lib both in server and web

I agree that it makes sense to keep the API the same for representing the PDF (i.e. the structure, what Page, View, and Text elements exist), but I would imagine there are substantial differences in how the PDF is rendered between the server and client. As an example, one would likely want to convert the PDF Document into a binary stream to send to a client from the server, but may want to render the PDF Document in an iframe or allow the user to download the binary with a link. As a user of this library, it actually wouldn't be too crazy for me to have to use a different function or component to render the Document differently from server to client. If what you're referencing is using something like SSR, that should still work fine if you take the separate renderer approach I'm suggesting. It would be even more explicit on each that the Document is being sent in a response as a stream or rendered in an iframe.

making Document the main root element which I think it makes sense from a declarative point of view

I also agree that it makes sense for the Document to the be root element of the PDF. My proposal doesn't change that. What it does change is that it separates out one of the two functions currently in Document, the rendering. I definitely agree (and like 😄 ) the API of having a root Document component for the actual PDF.

My idea about a BlobProvider was to allow for other converters of the PDFRenderer container. If, for example, we wanted to provide a stream of the PDF with a similar React-like interface, we might expose a StreamProvider. This is a bit contrived, but it follows the same SRP principle of providing a component (or more simply a function in node land) that does a single thing, namely converts a PDFRenderer container to some binary format to be used by some rendering mechanism (either streaming in a response or generating a URL on the client). To be explicit, the BlobProvider is not intended to be the root element of the PDF, it is simply a component that knows how to convert a PDF (rooted by a Document) into a renderable format (i.e. a blob).

The separation would actually make my PDF Documents more resuable, as I could generate them on either the client or server and have the freedom to render them as I pleased depending on the environment, making my decision explicitly in my own code rather than relying on the library's decision on how they should be rendered in each enviroment. I would appreciate the library providing the renderers (BlobProvider vs. StreamProvider [maybe *Renderer would be a better name...]) and could pick the one that suits my needs.

I suggested a DownloadLink because I believe in order to get a download link, users actually need to do some "complex" logic that is already coupled with how the library works. Namely, I need to know that the library is converting the PDFs into blobs, which will get passed to be in onRender, which I then need to know how to convert to a downloadable URL. I feel like having the library provide a more cohesive interface where, as a caller, I don't need to know about the details of going from PDF -> blob -> URL, I just care about getting a link that will download the file. So in my mind, it isn't as simple as rendering an <a/>, I am also needing to know how to convert from the blob to a URL that I can use as the href for my <a/>.

If that point about the DownloadLink and including it in the library or not is too contensious, I would be fine to ignore that suggestion as it's just another iteration on the API in an attempt to reduce coupling and increase cohesion. I think the key point of my proposal is to extract the rendering logic from the Document element and either provide some common renderers as part of the library or explain how clients should use the Document to render themselves.

It's almost like there are three distinct steps happening here

  1. Represent the PDF in react - you've done a great job of that with Document, Page, View and Text
  2. Convert the representation into a renderable format - this is happening in the PDFRenderer.* methods followed by pdf(container).to*() methods; each to*() is applying a different formatting operation to the PDF representation
  3. Render the chosen format - on the server, that's sending it in the response as a stream or buffer; on the client, that's displaying the blob in an iframe or making it available to download via a link.

I believe that the Document component is doing all three now, when they should instead be handled by simpler, more explicit components.

I understand that separation leads to a more complex API for the library, which could be solved by providing compound components that explicitly choose one of each functionality

const IFrameDocument = props => (
  <BlobProvider document={<Document {...props}/>} >
    {({ blob }) => <IFrameRenderer blob={blob}/>}
  </BlobProvider>
)

This says: "I'm converting my Document into a blob to be rendered in an iframe"

Sorry for such a long response, hope I did an ok job explaining my thoughts!

Thanks @phryneas !
It's great to have more people opinions. Sorry to hear this was the reason you couldn't use the lib yet. We will get this fixed soon.

Have a couple of questions about your proposal:

  • I do feel maybe shallow isn't the most appropriate prop name (also not the worst 😂). How would you call it?
  • Didn't get much what BlobViewer and DocumentViewer are. I think one of them are the IFrameViewer @bkoltai proposed, but didn't get the other one
  • Still don't see the need to have a DocumentDownloadLink component exported to have to have what you can simple achieve with the onRender callback and React state.
  • I think we can include BlobProvider object eventually in the library. I do feel that this:
<BlobProvider document={document}>
    {blob => (
      <React.Fragment>
        <BlobViewer blob={blob} />
        <a href={URL.createObjectURL(blob)}>{text}</a>
      </React.Fragment>
    )}
  </BlobProvider>

can be written in a more clear way as this

  <React.Fragment>
    <Document onRender={this.onRender}>{ // pages and stuff }</Document>
    <a href={URL.createObjectURL(this.state.blob)}>{text}</a>
  </React.Fragment>

I like render props also though, but idk, feels more "natural" for me the other way.

  • Interesting point about exporting the blob url every-time. I think it's a good call just passing the blob and making the user transform it to url if needed

Would like to hear your thoughts about this!

  • for shallow: what about invisible, hidden or a display={false} prop?
  • BlobViewer & DocumentViewer are essentially the same as the IFrameViewer above, but named by what they do, not how they do it. Most developers are not interested by the fact that there is an IFrame (much like they don't care about the fact that most libraries render a dialog by using a portal to render the dialog in a div that is mounted outside the react tree), but in the "what it does".
    A BlobViewer is passed a Blob and provides a viewer application for it. Likewise with the DocumentViewer.
    If there were multiple ways of displaying something, I'd go with <DocumentViewer method={"iframe"} /> or maybe with a DocumentIframeViewer - but I'd somewhere add the "Document" ;)
  • likewise, my thought with the render prop above was the following: The document is the main data layer, but (by name) not really the main interaction layer. A document is displayed, or provided for download - by something else. So I'd wrap in into a DocumentViewer or DocumentDownloadLink and I have the "UI-Layer" wrapped around the "Data Layer".
    Grabbing the state from inside the current Document component and keeping it in local component state kind of clutters the component around it and you already had that discussion above - saving it to the parent component's local state will cause a re-render, which again has to be avoided in some way. While this is manageable, it will definitely be a stumbling stone for some developers.
    With the render prop, parent component state can be avoided altogether and there is no risk of double-rendering. Also this way, it could even be used in a functional component, which would otherwise be impossible (aside from global state stuff like redux that should not really be used to store multi-MB-blobs ;) ).

Hey!
First let me drop a line saying how great I think it is to have your opinion 😄 I've been taking decisions on my own mostly since the beginning of the project and that's not easy either. So thanks about that. Good to see this is slowly becoming into a community-driven project.

Let's separate concerns a bit:

Viewers

Since the plan is to have a more customizable viewer in the future (again, as the one on the REPL), I think there's no doubt about introducing the concept of Viewer to the lib. I'm convinced about that, and I think you both have a good point about separating the data layer from the presentational one. I like the <PDFViewer component="iframe|Component"> API more than DocumentViewer and method prop. This will also enable people to create their own type of viewer if they want to.

I would like viewers to work like @phryneas suggested:

<PDFViewer>
  <Document>
    <Page>
      foo
    </Page>
  </Document>
</PDFViewer>

And not

<PDFViewer document={
<Document>
    <Page>
      foo
    </Page>
  </Document>
} />

BlobProvider

Also, Im keen on adding the BlobProvider as an extended API for the cases in which you just want to get the blob (isn't the BlobProvider another form of Viewer? One that renders nothing, but makes the blob accessible). Is BlobProvider the best name?

PDFDownloadLink

Having the BlobProvider I guess we can also add this piece to the API. You convinced me 😄 Would be a shortcut for using the BlobProvider directly, but won't make the library more heavy since there are just a couple of lines. Even this can be seen as a type of Viewer (maybe this was your whole point this time haha)

Usage with functional components

Also this way, it could even be used in a functional component, which would otherwise be impossible

True, but in the same way that using a controlled input is also impossible without state. This can be avoided easily by using recompose for instance (which I usually do and love).

Keeping current API active for the moment

Having said that, I think we still have to keep the old API alive:

<Document>
  <Page>{some stuff}</Page>
</Document>

Some reasons:

  • Not make this change so disruptive from older versions. We can add a deprecation warning in the future if we conclude viewers are the way to go
  • Have REPL and examples in general in a way they can be used both for Node and DOM
  • For people (like me yet 🙈) that (even if confusing) like a "cleaner" API

Internally, this will just render the iframe viewer. Not sure if possible code-wise, but worths the try.


PS: We should use react-pdf Spectrum's channel next time for this type of discussion. I think it can reach more people. My mistake of not having redirect the discussion there early 😄

I started implementing this new API here: https://github.com/diegomura/react-pdf/pull/285/commits/e87895cb896fdad5ced817ce893e3bfc877f1eb3

I would appreciate if someone can review it and check if we are in the right path.
I had one issue with this implementation though: cannot make the PDF to re-render when it changes. I tried by setting componentDidUpdate as you can see, but it enters in an infinte loop, because then the blob changes, document re-renders, blob changes, and so on.

Have to go now, but I would love to hear suggestions!
Thanks!!

Take a look at https://reactjs.org/docs/react-component.html#componentdidupdate :

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance. If you’re trying to “mirror” some state to a prop coming from above, consider using the prop directly instead. Read more about why copying props into state causes bugs.

As you are directly calling a method that results in setState, this is most likely the cause for your infinite loop.

Also, see the link about "you propably don't need derived state". My favorite aspect there is the part about memoization. Usually I don't deal with derived state any more, but I just call a method directly from the render method and memoize it's result.

Note that this can only be done with synchronous methods (toBlob seems to be async, so that might need some changes if you want to go that route) and is only useful if the method evaluation doesn't take too too long, so this could maybe not be suited for a very big pdf - I don't know how fast that rendering really is.

@diegomura, thanks for the thorough overview, and most of all, for taking feedback and outside opinion! I think we're converging on a good solution.

Viewers

I respect your preference of using @phryneas's suggestion, though I want to leave one more thought in defense of my render props API. In my (relatively extensive at this point) usage of the library, I have found it helpful to define specific PDF Documents as named components. For example, I have a Document component that looks something like

const SchedulePDF = (props) => <Document>...</Document>

Following the line of thinking that there are two things going on here (data and presentation), and given that the render props (or children) pattern is typically used to give rendering control to the client, it would seem to make sense to me to have an API where the data is passed to the presentation component as a prop, and the customization of that presentation is allowed for using render props, like

<DocumentViewer document={<SchedulePdf someProp={thing1} someOtherProp={thing2}/>}>
  {(blob) => <iframe src={<do things with blob>} />}
</Document>

You could use the children pattern as well, though I think it's less explicit and harder to reason about. In your proposal, you're passing presentation logic as a prop the the DocumentViewer and using children to pass data, which I guess isn't deemed bad practice anywhere, it just seems like it's the opposite way most libraries seem to structure their APIs. As a user, I would also need some documentation to understand how the component prop is used internally rather than seeing explicitly through the render props pattern what the supplied data is from the DocumentViewer and making my own decisions about which props I care about/need to do my presentation.

Additionally, if someone wanted to use the default DocumentViewer renderer (maybe the current iframe strategy), they would omit passing a function as children and you'd have

<DocumentViewer document={<SchedulePdf ... />}/>

(P.S. I draw a lot of inspiration for my component design from Kent C Dodds and specifically the downshift library that powers all of PayPal's dropdowns)

BlobProvider

Naming is hard! I view the role of the BlobProvider to be a converter of the raw PDF Document (the data) to a renderable format (a blob on the client, but possibly something else on the server like a stream or buffer). I don't consider it a Viewer because you can't render it directly as a React component. In my mind, Viewers should be things that look reasonable when rendered as a React component. In my "Viewers" section above, the DocumentViewer is acting as this BlobProvider.

Another term that might be useful to us could be "Adapter", as that sometimes suggests connecting one format or system to another. I think that fits well here because we're converting a raw PDF Document to a format that is renderable by a Viewer. Ultimately the choice of which Adapater you use would be dependent on the Viewer you're planning on using.

PDFDownloadLink

Exactly what I was trying to get at! And maybe the library providers a similar PDFViewer


Excited to see this come out!

Okay, I definitely see your problem - I tried it locally and it had me reboot a few times because it took up all my precious ram ^^

Every shallow equality check on the document-child-prop as well as the blob will fail as those are always new instances. :/

I've got to go to sleep now, but I'll do some more experimentation tomorrow in the evening.

Hey @bkoltai .
Tricky scenarios.

Viewers

I understand your point of view, but I think I disagree in one fact: considering the PDF elements as _data_. For me (and because they are React elements), they are just markup, that are going to be rendered somewhere. Looked from this point of view, in concept <Page /> is not different than <div />.

You should use the BlobProvider if you want to render the document as you proposed, which I think it makes more sense also. If you are using the PDFViewer and rendering the iframe yourself, the container is no "viewer" at all right? It's basically the blob "provider".

Even though it's being widely used, I'm very conservative about passing elements through props, specially when the element tree can be really big (such as in react-pdf if you're rendering a big file). If big, it feels weird and makes your JSX less readable than just using. I know you can write your PDF document in a separate component (and I also think it's neat), but we shouldn't force it.

BlobProvider

Going to thing about Adapter vs Provider. I like what the concept, but Provider feels more React-friendly. But I don't have strong opinions on any of these.

PDFDownloadLink

Glad it's what you though about it! You totally convinced me about this. There's no harm on adding it

Hello @diegomura (and everyone) I just wanted to say hello and that I'm really pleased with your library. I'm currently building a budgeting application for a company and part one of the requirements is that the user is able to print their new budget in PDF form. I think your library looks amazing and that it will solve my technical issues.

Still, I've been reading through this thread and am a little confused...

My question is: can I pass Redux store props directly to my react-pdf and create a PDF on the fly? Do I need to download and use the REPL for this functionality? Can I simply send redux props to my react-pdf and render it afterwards? What would be the easiest solution here?

Thanks so much for your time in clarifying this process for me,

Aaron

Hey @AJTJ . I moved your question to our spectrum channel, which is more suitable for this type of questions. You can find an answer there 😄

Amazing, I'll follow-up over there.

Hi Diego,

~Any chance we could at least get access to the blob in the interim?~

~IE doesn't support data-uri for iframe src. They, of course, have a IE only way of doing things and without the blob, my IE users are dead in the water.~

I just noticed alpha-15 provides the blob in document.onRender. Thanks!

Hello again :) Thanks for the great library!

I think it would be more convenient to just have a function convertDocumentToBlob(<Doc />) because once you have a blob you could use it on mobile to save a file on the device, on the web to force download, or to render it in a new tab etc.

If you have to render it in the dom with ReactDOM.render(<Doc />, document.getElementById('root')); just so you could get the blob, then it won't work for any other environment than web because you don't have ReactDOM there.

Thanks for the input @pavle-lekic-htec

Why not using the upcoming <BlobProvider /> for the things you mentioned?

Ex.

<BlobProvider document={<Doc />}>
  {({ blob }) => {
    // Do any of those things here
  }}
</BlobProvider>

I cannot think on a technical issue about adding a convertDocumentToBlob, but I just don't what to overcomplicate react-pdf's public API with so many helper functions when you can already achieve the same things with what we already have (or will have once I can wrap up this issue)

Sure, you could do that, but you have to mount it somewhere, you depend on ReactDOM, which you don't have on mobile.

I noticed pdf() function inside index.js, but it isn't mentioned anywhere in the documentation, and it looks like it's related to creating a blob, can we use this function somehow to get a blob?

Yes. pdf is currently available, but it does not receives the document tree as argument, but the internal element document structure. To be honet, pdf is not part of the public API, thats why it's not documented.I think it's not bad idea to make it more usable and part of the public API. I think this can be also handy for APIs that want to return document content.

Having something like:

toPDF(<Document />).toBlob();
toPDF(<Document />).toBuffer(); // Node
toPDF(<Document />).toString(); 

Going to work on the idea and check how it works

If you need to open generated pdf on new window, without click on download link, use below code

const PrintDoc = ({ values }) => (
    <Document>
        <Page size="A6" style={styles.page}>
            <View style={styles.section}>
                <Text>Section #1</Text>
            </View>

        </Page>
    </Document>
);

const openPDF = (url) => {
    window.open(url, '_blank');
};

export const PDFPrint = () => {
    return (
        <PDFDownloadLink document={<PrintDoc />} fileName="somename.pdf">
            {({ blob, url, loading, error }) =>
                loading ? 'Loading document...' : openPDF(url)
            }
        </PDFDownloadLink>
    );
};
Was this page helpful?
0 / 5 - 0 ratings

Related issues

saratonite picture saratonite  ·  3Comments

pavle-lekic-htec picture pavle-lekic-htec  ·  4Comments

mdodge-ecgrow picture mdodge-ecgrow  ·  3Comments

benbenedek picture benbenedek  ·  3Comments

diegomura picture diegomura  ·  4Comments