diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
14.83k stars 1.18k forks source link

Image not updating when Image src is updated #343

Closed serkyen closed 6 years ago

serkyen commented 6 years ago

OS: mac os 10.13.4

React-pdf version: 1.0.0-alpha.17

Description: I am trying to update the PDF with an image dynamically after making changes to a form. Here is the code...

generateOrderPDF = (fields) => {

    var businessData = this.props.user.business;
      if (fields.contact != undefined || fields.contact != '') {
        var orderName = this.generateOrderName();
        var contactName = this.generateContactName();
      } else {
        var orderName = "";
        var contactName = "";
      }

      if (fields.designs.length > 0 && fields.designs[0].image != undefined) {
        var designThumb = fields.designs[0].image;        
      } else {
        var designThumb = '';
      }

      console.log(designThumb)

      return (
          <Document title="Order">
            <Page wrap size="A4" style={styles.page}>

              <View style={styles.header}>

                  <View style={{textAlign:'center'}}>
                    <Image
                      style={styles.logo}
                      src={businessData.logo === '' ? null : businessData.logo}
                    />
                    <Text style={{padding: 5, fontSize:10}}>{businessData.name}</Text>
                  </View>

                  <View style={styles.headerLeftWrapper}>
                    <Text>Order Name: {orderName === "" ? null : orderName}</Text>
                    <Text>Status: {fields.status === "" ? null : fields.status}</Text>
                    <Text>Ordered by: {contactName === "" ? null : contactName}</Text>
                    <Text>Ordered for: {fields.orderedFor === "" ? null : fields.orderedFor}</Text>
                    <Text>Note: {fields.note === "" ? null : fields.note}</Text>
                  </View>

                  <View style={styles.headerRightWrapper}>
                    <Text>Date: {moment(new Date()).format('DD/MM/YYYY')}</Text>
                    <Text>Occasion Date: {moment(fields.occasionDate).format('DD/MM/YYYY')}</Text>
                    <Text>Receiving Date: {moment(fields.receivingDate).format('DD/MM/YYYY')}</Text>
                  </View>

              </View>

              <Image style={{width:400, height:400, marginVertical: 15, marginHorizontal: 100}} src={designThumb} />

              <View style={styles.section}>
                <Text style={styles.body}></Text>
              </View>

              <View style={[styles.section, { bottom: 1 }]}>
                {Footer}
              </View>

            </Page>

          </Document>
      )

}
<BlobProvider document={this.generateOrderPDF(this.props.form.getFieldsValue())}>
{({ url }) => (
  <a href={url} target="_blank" style={{marginRight:'5px'}} className="download-pdf-btn ant-btn btn-secondary ant-btn-primary">Print</a>
)}
</BlobProvider>

All other fields on the PDF update accordingly when the value is changed on the form, but the image does not. I am certain that the designThumb is a valid link to an image because...

What's unusual is that when I go to the playground, and remove the link from one of the Image's src, it updates as it should with no issues.

Am I doing something wrong?

serkyen commented 6 years ago

I have tried everything but still unable to get this to work.

I have tried storing and getting the values from...

All do the same thing which is, gets the value correctly only when the page first renders, but making any changes after the page has loaded, and clicking on the print button again does not update the image src value, only the other values. Seems that the Image src value is set in stone when the PDF first loads.

How come Text can update but Image can't? Please help...I am stuck on this issue for days now.

Thanks in advance.

diegomura commented 6 years ago

Hey @serkyen Sorry to hear that. I'm back to work now. I promise I'll check this issue tomorrow

serkyen commented 6 years ago

@diegomura Thank you :)

diegomura commented 6 years ago

@serkyen fixed!

Silly issue, but also hard to see beforehand. I also implemented cache for images. So now, if you're rendering an image that was in the document in the past, it won't trigger another network request. This will make re-rendering way more faster. You can disable it of course by passing cache={false} to Image.

Tomorrow I'll publish a new version and will be available for you to use

serkyen commented 6 years ago

@diegomura Awesome! Am waiting for the new version :) Thanks for the cache feature as well! 👍

serkyen commented 6 years ago

@diegomura It looks like this is still not working for me after updating to 1.0.0-alpha.19

Couple issues...

Also, I get the following errors on the console when I change the src of the Image.

serkyen commented 6 years ago

@diegomura Here are the errors I get each time I change the Image src (making the selection on the form).

image

diegomura commented 6 years ago

What do you mean by click the print again? I think I will have to know a bit more about how you're using the library in order to understand what's going on. I tried again and image upload seems to be working fine. This is an example of how you can test image upload:

class MyDocument extends React.Component {
  state = { image: 'someurl' }

  componentDidMount() {
    setTimeout(() => {
      setState({ image: 'otherurl' }) 
    }, 1000)
  }

  render() {
    return (
      <PDFViewer>
        <Document>
          <Page>
            <Image src={this.state.image} />
          </Page>
        </Document>
      </PDFViewer>
    )
  }
}
serkyen commented 6 years ago

Ok here is how I am using it....

import React from 'react';
import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data';
import {browserHistory, withRouter} from 'react-router';
import { BlobProvider, PDFViewer, PDFDownloadLink, Page, Text, View, Image, Document, StyleSheet } from '@react-pdf/renderer/dist/react-pdf.browser.cjs.js';
import { styles } from '../pdf/Styles.js';
...

class OrderForm extends React.Component {
    constructor(props) {
      super(props);
      // this.state = {
      // };
    };

    generateOrderPDF = (fields) => {

        var businessData = this.props.user.business;

        if (fields.contact != undefined || fields.contact != '') {
          var orderName = this.generateOrderName();
          var contactName = this.generateContactName();
        } else {
          var orderName = "";
          var contactName = "";
        }

        if (this.props.designsList.length > 0 && this.props.designsList[0].image != undefined) {
          var designThumb =  this.props.designsList[0].image;
        } else {
          var designThumb = '';
        }

        console.log(designThumb)

        return (
            <Document title="Order">
              <Page wrap size="A4" style={styles.page}>

                <View style={styles.header}>

                    <View style={{textAlign:'center'}}>
                      <Image
                        style={styles.logo}
                        src={businessData.logo === '' ? null : businessData.logo}
                      />
                      <Text style={{padding: 5, fontSize:10}}>{businessData.name}</Text>
                    </View>

                    <View style={styles.leftWrapper}>
                      <Text>Order Name: {orderName === "" ? null : orderName}</Text>
                      <Text>Status: {fields.status === "" ? null : fields.status}</Text>
                      <Text>Ordered by: {contactName === "" ? null : contactName}</Text>
                      <Text>Ordered for: {fields.orderedFor === "" ? null : fields.orderedFor}</Text>
                      <Text>Note: {fields.note === "" ? null : fields.note}</Text>
                    </View>

                    <View style={styles.rightWrapper}>
                      <Text>Date: {moment(new Date()).format('DD/MM/YYYY')}</Text>
                      <Text>Occasion Date: {moment(fields.occasionDate).format('DD/MM/YYYY')}</Text>
                      <Text>Receiving Date: {moment(fields.receivingDate).format('DD/MM/YYYY')}</Text>
                    </View>

                </View>

                <View style={styles.section}>
                  <View style={[styles.leftWrapper, { width:'150', textAlign:'center' }]}>
                    <Image cache={false} style={{width:150, height:150}} src={designThumb} />
                  </View>
                  <View style={[styles.rightWrapper, { width:'300' }]}>
                  </View>
                </View>

                <View style={styles.section}>
                  <Text style={styles.body}></Text>
                </View>

                <View style={[styles.section, { bottom: 1 }]}>
                  {Footer}
                </View>

              </Page>

            </Document>
        )

    }

    render() {

      const { getFieldDecorator, getFieldValue, getFieldsValue } = this.props.form;

      return (

        <div>

            <Form onSubmit={this.handleSubmit}>

              <FormItem style={{float: 'right', zIndex: '1000', margin: '0px'}}>

                {!this.props.loading ?
                  <BlobProvider document={this.generateOrderPDF(this.props.form.getFieldsValue())}>
                  {({ url }) => (
                    <a href={url} target="_blank" style={{marginRight:'5px'}} className="download-pdf-btn ant-btn btn-secondary ant-btn-primary">Print</a>
                  )}
                  </BlobProvider>
                : null}

                  <Button loading={Session.get('processing')} className="btn-secondary" disabled={this.props.uploading} type="primary" htmlType="submit">Save</Button>

              </FormItem>

               <FormItem
                     colon={false}
                     label="Status"
                >
                     <div>
                         {getFieldDecorator('status', {
                             initialValue: this.props.doc.status,
                             onChange:this.setFormValues,
                             rules: [{
                                  required: true,
                                  message: 'Please select a status.'
                             }]
                          })(
                          <Select
                               showSearch
                               optionFilterProp='label'
                               filterOption={(input, option) => option.props.label.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                            >
                                  <Option key="0" value="Unconfirmed" label="Unconfirmed">Unconfirmed</Option>
                                  <Option key="1" value="Confirmed" label="Confirmed">Confirmed</Option>
                                  <Option key="2" value="Processing" label="Processing">Processing</Option>
                                  <Option key="3" value="Completed" label="Completed">Completed</Option>
                                  <Option key="4" value="Cancelled" label="Cancelled">Cancelled</Option>
                                  <Option key="5" value="Refunded" label="Refunded">Refunded</Option>
                          </Select>
                          )}
                     </div>
                 </FormItem>

                  ...

            </Form>
        </div>

      )
    }
}

export default withTracker( props => {
  const user_sub = Meteor.subscribe('users.safeFields');
  const loading = !user_sub.ready();
  const user = Meteor.users.findOne(Meteor.userId());
  const designsList = Session.get('designsList');
  return {
    loading,
    designsList,
  };
})(Form.create({})(withRouter(OrderForm)));

I have omitted a lot of code because of irrelevance. Form is from antD Form component.

diegomura commented 6 years ago

Still... tried again using the same principles you're using and everything worked fine:

const MyDocument = ({ image }) => (
  <Document>
    <Page style={styles.page} wrap debug>
      <Image src={image} />
    </Page>
  </Document>
)

class App extends React.Component {
  state = { image: '/quijote1.jpg', counter: 10 }

  componentDidMount() {
    setInterval(() => {
      if (this.state.counter > 0) {
        this.setState({ image: this.state.image === '/quijote1.jpg' ? '/quijote2.png' : '/quijote1.jpg' })
      }
    }, 2500)
  }

  render() {
    return (
      <BlobProvider document={<MyDocument image={this.state.image} />}>
        {({ url }) => {
          return <a href={url}>Print</a>
        }}
      </BlobProvider>
    )
  }
}

Please observe that you can now define your PDF document as a separate component and pass <MyDocument someProps /> to the BlobProvider. I don't know what might be happening, but that error means that the lib is trying to edit an already rendered PDF instance. Internally, it creates a new instance on each rerender

serkyen commented 5 years ago

Ok I think the issue is revolving around a CORS problem. I am getting this error as well...

Access to fetch at 'https://link-to-image-at-amazon-S3.jpg' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Any ideas?

diegomura commented 5 years ago

Not sure how a cors issue can cause the error message you sent. But I would suggest to resolve that problem first. If the lo cannot get the image still renders.

There’s no much I can do to solve that issue. It seems that you cannot access the image from your dev server. You will have to change access headers of your resources

serkyen commented 5 years ago

@diegomura Good news is that I solved the errors with a setTimeout, and I also fixed the CORS problem and I am able to render the PDF with the updated image, but there seems to be one more error showing up.

Here is the code I have now...

class OrderForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      image: Session.get('designsList').length > 0 ? Session.get('designsList')[0].image : ''
    };
  };
  ...
//Runs each time a design is selected on the form.
onDesignSelect = (v, index) => {

    Session.set('pdfPrintable', false);

    ...

    Session.set('designsList', z);
    this.setState({
      image: Session.get('designsList')[0].image
    });

    setTimeout(function (){
      Session.set('pdfPrintable', true);
    }, 200);

  }
generateOrderPDF = (fields) => {
    return (
        <Document title="Order">
          <Page wrap size="A4" style={styles.page}>
            <View style={styles.section}>
                <Image cache={false} style={{width:150, height:150}} src={this.state.image} crossorigin="anonymous"/>
            </View>
          </Page>
        </Document>
    )
}
{this.props.pdfPrintable ?
    <BlobProvider document={this.generateOrderPDF(this.props.form.getFieldsValue())}>
    {({ url }) => (
        <Button href={url} target="_blank" type="primary">Print</Button>
    )}
    </BlobProvider>
: null}

So if I remove the setTimeout function and just Session.set('pdfPrintable', true);, the stream.push errors comes back. There's probably a better way to fix this but I guess it will be ok for now.

So here is the issue now...

When the page first loads, all is well. No errors and the PDF prints as desired. As soon as I make a change (select a design from the form which will update this.state.image), the PDF still updates and renders ok, but I get this error in the console...

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in InternalBlobProvider (created by BlobProvider) in BlobProvider (created by OrderForm)

If I change the design again, the error does not show up again. Basically, the error only shows up the first time I change this.state.image but not anytime after. I guess I could ignore this error because everything is working anyway but looking at the structure of the code above, do you know how I can avoid this error or why it is showing up?

Thanks for your all your help by the way...really appreciate it.

dulcineapena1 commented 4 years ago

anyone else with same CORS problem ? crossorigin="anonymous" and cache={false} doesn't work for me :(

rakeshbijoriyagate6 commented 4 years ago

Same CORS issue with me I used crossorigin="anonymous" and cache={false} doesn't work for me. Access to XMLHttpRequest at 'https://dgzr31xopt5l5.cloudfront.net/profile_pics/FnB7YJOtWsVyvHN8dKEyvbNmV9V21570438881789.png' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Anyone has solved this issue. Thanks in advance :)

MianUmer486 commented 4 years ago

i am facing this CORS issue in google chrome only , in firefox it works perfectly but in chrome i get the CORS issue. Kindly help.

serkyen commented 4 years ago

Hey all. Just for reference, this is how I am loading images on the PDF and everything works.

<Image cache={false} src={image+"?rnd="+Math.random()} crossorigin="anonymous"/>

chetankumars commented 4 years ago

### Am unable to retrieve the images on the browser.

                    <Image style={{ width: 400, height: 400, marginVertical: 15, marginHorizontal: 100,border:'1px solid red' }}
                        src="https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg"

                    />

                        <Text style={styles.movieTitle}>  音声サービス5年特割パック</Text>

And if i register a font which is support to Japanese character Font.register(${__dirname}fonts/Nasu-Regular.ttf, { family: "Nasu-Regular" }); Error: Unknown font format at Object.push../node_modules/@react-pdf/fontkit/dist/fontkit.browser.es.js.fontkit$1.create (fontkit.browser.es.js:68) at FontSource._callee2$ (font.js:35) at tryCatch (runtime.js:45) at Generator.invoke [as _invoke] (runtime.js:271) at Generator.prototype. [as next] (runtime.js:97) at asyncGeneratorStep (asyncToGenerator.js:3) at _next (asyncToGenerator.js:25)

### Please help...

alun777 commented 4 years ago

Hey all. Just for reference, this is how I am loading images on the PDF and everything works.

<Image cache={false} src={image+"?rnd="+Math.random()} crossorigin="anonymous"/>

Hey @serkyen, thanks for your reference but I don't think there's a property crossorigin on <Image>

mikesosa commented 3 years ago

Any update on this issue?

ajay-bsoft commented 2 years ago

Hey all. Just for reference, this is how I am loading images on the PDF and everything works.

<Image cache={false} src={image+"?rnd="+Math.random()} crossorigin="anonymous"/>

Unfortunately cache={false} is not working. The random suffix makes the URL unique so the image is freshly downloaded and not using the cache, and this works. check also the observation at https://github.com/diegomura/react-pdf/issues/1283#issuecomment-833892090

poshan-kandid commented 2 years ago

Access to XMLHttpRequest at 'https://storage.googleapis.com/samclientpreprod/MicrosoftTeams-image%20(1)-1637660753530-740499763.png' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I am facing this issue when I am fetching image dynamically

KirankumarDafda commented 2 years ago

I have same issue, and almost everyone is talking about Amazon S3 bucket, And I am using url of my domain to show an image, Where I am getting IMAGE URL from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

https://github.com/diegomura/react-pdf/issues/929

Please suggest anything that works with normal domain url image

dardandauti commented 2 years ago

Is there an update on this issue for using a simple url?

Tried using [https://sv.wikipedia.org/wiki/Playstation_5#/media/Fil:PlayStation_5_and_DualSense_with_transparent_background.png] and other public images, but cannot load them into the PDF?

Shaif821 commented 2 years ago

Is there an update on this issue for using a simple url?

Tried using [https://sv.wikipedia.org/wiki/Playstation_5#/media/Fil:PlayStation_5_and_DualSense_with_transparent_background.png] and other public images, but cannot load them into the PDF?

Were you able to fix this?

jkm96 commented 10 months ago

@diegomura Good news is that I solved the errors with a setTimeout, and I also fixed the CORS problem and I am able to render the PDF with the updated image, but there seems to be one more error showing up.

Here is the code I have now...

class OrderForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      image: Session.get('designsList').length > 0 ? Session.get('designsList')[0].image : ''
    };
  };
  ...
//Runs each time a design is selected on the form.
onDesignSelect = (v, index) => {

    Session.set('pdfPrintable', false);

    ...

    Session.set('designsList', z);
    this.setState({
      image: Session.get('designsList')[0].image
    });

    setTimeout(function (){
      Session.set('pdfPrintable', true);
    }, 200);

  }
generateOrderPDF = (fields) => {
    return (
        <Document title="Order">
          <Page wrap size="A4" style={styles.page}>
            <View style={styles.section}>
                <Image cache={false} style={{width:150, height:150}} src={this.state.image} crossorigin="anonymous"/>
            </View>
          </Page>
        </Document>
    )
}
{this.props.pdfPrintable ?
    <BlobProvider document={this.generateOrderPDF(this.props.form.getFieldsValue())}>
    {({ url }) => (
        <Button href={url} target="_blank" type="primary">Print</Button>
    )}
    </BlobProvider>
: null}

So if I remove the setTimeout function and just Session.set('pdfPrintable', true);, the stream.push errors comes back. There's probably a better way to fix this but I guess it will be ok for now.

So here is the issue now...

When the page first loads, all is well. No errors and the PDF prints as desired. As soon as I make a change (select a design from the form which will update this.state.image), the PDF still updates and renders ok, but I get this error in the console...

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in InternalBlobProvider (created by BlobProvider) in BlobProvider (created by OrderForm)

If I change the design again, the error does not show up again. Basically, the error only shows up the first time I change this.state.image but not anytime after. I guess I could ignore this error because everything is working anyway but looking at the structure of the code above, do you know how I can avoid this error or why it is showing up?

Thanks for your all your help by the way...really appreciate it.

Hello @serkyen How did you fix the CORS issue?