I have a form and I wish to have a print button that renders the data that is on the form into a PDF. If I make a change to a field, and click the print button again, I want that new data to be on the PDF.
So far, I have been able to use a BlobProvider component to create the PDF but getting dynamic data from the form to the PDF is proving to be quite difficult and unusual.
After many different approaches, this is the closest I have come...
<BlobProvider document={this.OrderPDF()}>
{({ url }) => (
<a href={url} target="_blank">Print</a>
)}
</BlobProvider>
OrderPDF = () => {
var { form } = this.props;
var note = form.getFieldValue('note');
return (
<Document title="Order">
<Page size="A4" style={styles.page}>
<View style={styles.section}>
{Header}
</View>
<View style={styles.section}>
<Text>{note}</Text>
</View>
<View style={styles.section}>
<Text style={styles.body}>Section #2</Text>
</View>
<View style={[styles.section, { bottom: 1 }]}>
{Footer}
</View>
</Page>
</Document>
)
}
Here's the unusual part...
When I first load the page, whether there is data in the note field or not, the first time I click on the Print button, the PDF shows nothing for the note. Then if I make any change to the note field (ie. clear some characters), then print the PDF again, it shows the correct data, but only for the first change I made.
So for example...
At first I was thinking that the reason is because the PDF is only being rendered once upon load, so that's why I moved it into a function to try to get it to render new values each time I click Print, but it seems to be rendering only twice...once upon load, then a second time when I click the print button, and then every time after that it always just shows the PDF from the second time it rendered.
Why is this happening and is there anyway I can retrieve the new values each time I click Print?
How is it that you receive the form as prop? Don’t know if it’s related but I would like to know how’s that working to figure out why is that happening.
Would be great if you can share a sandbox that I can execute to replicate this
I am using Ant Design's Form component along with withTracker...
import Form from 'antd/lib/form';
class OrderForm extends React.Component {
...
render() {
...
return (
<Form onSubmit={this.handleSubmit}>
...
</Form>
)
}
export default withTracker( props => {
...
})(Form.create()(withRouter(OrderForm)));
Hey @serkyen
Never used Ant Design, so I cannot say for sure what's going on internally with that form prop.
However, I isolated this in a simple example, and filling PDF data from forms is working.
Please try this snippet:
import React from 'react'
import { BlobProvider, Document, Page, Text, View, Link, StyleSheet } from '@react-pdf/renderer'
const MyDoc = ({ value }) => (
<Document>
<Page wrap>
<Text style={{ padding: 40 }}>{value}</Text>
</Page>
</Document>
)
class App extends React.Component {
state = { value: 'Hey' }
render() {
return (
<div style={{ height: '100%' }}>
<input value={this.state.value} type="text" onChange={(e) => this.setState({ value: e.target.value })} />
<BlobProvider document={MyDoc({ value: this.state.value })}>
{({ url }) => <iframe src={url} style={{ width: '100%', height: '100%' }} />}
</BlobProvider>
</div>
)
}
}
In here I'm just using React state to save form data, and onChange event instead of submit.
The PDF document get's updated in each time the component re-renders (state or props change). What I suspect is going for you is that form reference does not change, making the component not to re-render and therefore not calling this.OrderPDF each time form data changes. I infer this because of how you access form data (form.getFieldValue).
All I can do is asking to follow a more "react" approach on how to handle forms. But the issue is not on react-pdf, but how it integrates with your form library.
Hope this answer helped to figure out what's going on
Hi @diegomura,
This still does not work for me. I have tried so many different ways over the course of the last couple of days but just can't figure out what's going wrong here.
Here is what I have...
import React from 'react';
import Form from 'antd/lib/form';
import { BlobProvider, Document, Page, Text, View, Link, StyleSheet } from '@react-pdf/renderer';
const OrderPDF = ({testing}) => (
<Document title="Order">
<Page size="A4">
<View>
<Text>{testing}</Text>
</View>
</Page>
</Document>
);
class OrderForm extends React.Component {
constructor(props) {
super(props);
this.state = {
testing: "Hey"
};
};
render() {
...
return (
<Form onSubmit={this.handleSubmit}>
<BlobProvider document={OrderPDF({ testing: this.state.testing })}>
{({ url }) => <iframe src={url} style={{ width: '100%', height: '100%' }} />}
</BlobProvider>
<FormItem
colon={false}
label="Testing"
>
{getFieldDecorator('testing', {
initialValue: this.state.testing
})(
<Input onChange={(e) => this.setState({ testing: e.target.value })}/>
)}
</FormItem>
<Button type="primary" htmlType="submit">Save</Button>
</Form>
)
}
export default withTracker( props => {
...
})(Form.create()(withRouter(OrderForm)));
When I load the page, the PDF shows up with 'Hey'.
When I type into the input, after each key press, I can see the PDF reloading, but it still shows up with 'Hey' and not what I type in. When I check the state in the console, I see that it has changed to what I typed.
So each time I press a key, the PDF is reloading and the state is changing, but the PDF is still rendering the same.
Ok wow, I have found the issue.
It all came down to simply not having 'wrap' in the Page component.
You might want to check this but if I leave 'wrap' out, then the issue is there (always renders the same and does not pick up state changes). If I add it in, things work as they should and the PDF renders with the updated value. Try testing this with your snippet above to see if you can replicate.
So now that's out of the way, going back to the initial problem of getting dynamic data, I am trying this...
generateOrderPDF = (fields) => {
return (
<Document title="Order">
<Page wrap>
<View>
{console.log(fields.note)}
<Text>{fields.note}</Text>
</View>
</Page>
</Document>
)
}
...
<BlobProvider document={this.generateOrderPDF(this.props.form.getFieldsValue())}>
{({ url }) => (
<a href={url} target="_blank">Print</a>
)}
</BlobProvider>
the {console.log(fields.note)} is outputting the correct value to the console but getting this error upon load and the print button is not clickable...
Uncaught (in promise) TypeError: instance.getComputedStyles is not a function
at getFragments (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487127)
at modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487170
at Array.forEach (<anonymous>)
at getFragments (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487145)
at getAttributedString (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487196)
at Text.get (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487418)
at Text.layoutText (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487272)
at Text.measureText (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:487286)
at External.data (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:600915)
at Array.eval (eval at buildJSCallerFunction (modules.js?hash=b436a6b8165c7ed9560b743a7f1192d565b9fbe0:602520), <anonymous>:1:86)
Also should mention that I tried the state approach but same error shows up.
I recently had that error and there is a fix pending release for it. You may be getting it when testing is an empty string. Try handling the empty string case explicitly in your Text component and render null instead when empty
<Text>{texting === “” ? null : testing}</Text>
@bkoltai Thanks so much for your response.
All good now 👍
Fix is going to be published soon 😊
@diegomura Dear Diego, how to pass variable but not using blob? because if I used blob, all the form input will be laggy,
Thank you
Most helpful comment
I recently had that error and there is a fix pending release for it. You may be getting it when
testingis an empty string. Try handling the empty string case explicitly in yourTextcomponent and rendernullinstead when empty