React-to-print: onBeforeGetContent is not printing when a content changes

Created on 25 Sep 2019  路  18Comments  路  Source: gregnb/react-to-print

The onBeforeGetContent is working properly when following the example files and it changes the content of the text from _'00000000'_ to _"text changed"_.
However, the changes is not reflected neither in the printing UI nor in the printed page itself.

bug unverified

Most helpful comment

@MatthewHerbst tested and confirmed your snippet worked great!
(one small fix is to make initialization of resolve a function)

  const [counterOptions, setCounterOptions] = useState({ counter: 0, resolve: () => undefined });

All 18 comments

Hello, sorry for the very delayed response. When I run the example locally the text changes in the browser window and is properly changed in the print window. Can you please explain how you were able to see it not work properly?

Hi!

I have a PrescriptionPad component and the content of that depends on a state. When I changed the state inside onBeforeGetContent the UI changed but not reflected in print preview. I believed the setState is asynchronuos that's why its not reflected on the print preview. How to achieve that? Thanks!

<ReactToPrint
  trigger={() => (
    <Button size="small" type="primary">
      <Icon type="printer" /> Print Prescription
    </Button>
  )}
  content={() => prescriptionPadRef.current}
  onBeforeGetContent={() =>
    //perform setState here
  }
/>

Hi @christianandrei you need to return a Promise that resolve when the setState call completes. setState has a callback that is called after changes have been committed, so you just need to resolve the promise there:

handleBeforeGetContent = () => {
  return new Promise((resolve, reject) => {
    this.setState({ key: 'updatedValue' }, () => resolve());
  });
}

Hi @christianandrei you need to return a Promise that resolve when the setState call completes. setState has a callback that is called after changes have been committed, so you just need to resolve the promise there:

handleBeforeGetContent = () => {
  return new Promise((resolve, reject) => {
    this.setState({ key: 'updatedValue' }, () => resolve());
  });
}

I'm using react hooks and setState (useState) there has no callback function. May I know how to do it in react hooks version? Thank you very much!

@christianandrei Just try to return Promise.resolve() in function where you do setState.

handleBeforeGetContent = () => {
  setState({ key: 'updatedValue' });
  return Promise.resolve();
}

@sergeyshmakov that won't work because setState is asynchronous, meaning you must wait for it to finish before resolving the promise.

@christianandrei I haven't tested it, but I think you want something like this:

const MyFunctionalComponent = (props) => {
  const [counterOptions, setCounterOptions] = useState({ counter: 0, resolve: undefined });

  useEffect(() => {
    counterOptions.resolve();
  }, [counterOptions]);

  return (
    <ReactToPrint
      onBeforeGetContent={() =>
        return new Promise((resolve) => {
          setCounterOptions(prevOptions => ({ counter: prevOptions.counter + 1, resolve }));
        });
      }
    />
  );
}

@MatthewHerbst Actually it will works. Because of Promise.resolve() create asynchronous call.

@sergeyshmakov It will not work. Promise.resolve does create a Promise, but it will not wait to resolve that promise until the state update is complete, which is what we are trying to do here.

@MatthewHerbst How I understand - you guys need to get actual HTML after setState. Just watch this short example with console.log.

https://jsfiddle.net/sergeyshmakov/jhdpvtu5/

@sergeyshmakov that merely _appears_ to work because the state update is happening quickly. There is no guarantee that state updates will always happen quickly.

@MatthewHerbst Partly you're right but this solution is ok if you dont do async actions in setState. And this is not depends on DOM-size of react component.

Your solution is more "honest" yes, but we need to check that resolve exist and also remove it from state after resolve() call.

const MyFunctionalComponent = (props) => {
  const [counterOptions, setCounterOptions] = useState({ counter: 0, resolve: undefined });

  useEffect(() => {
    const {resolve, ...otherState} = counterOptions;
    if (resolve) {
        resolve();
        setCounterOptions({ ...otherState, resolve: undefined });
    }
  }, [counterOptions]);

  return (
    <ReactToPrint
      onBeforeGetContent={() =>
        return new Promise((resolve) => {
          setCounterOptions(prevOptions => ({ counter: prevOptions.counter + 1, resolve }));
        });
      }
    />
  );
}

@sergeyshmakov I think you misunderstand a bit. setState itself is asynchronous, regardless of what state you actually change.

Also, there is no need to clear out the resolve since the useEffect hook will _only_ run when the counter changes, and the counter can only change in the one spot where we specify changing it, where we also set the new resolve. In fact, resetting the resolve as you do in your example causes a needless re-render of the component.

@sergeyshmakov that won't work because setState is asynchronous, meaning you _must_ wait for it to finish before resolving the promise.

@christianandrei I haven't tested it, but I think you want something like this:

const MyFunctionalComponent = (props) => {
  const [counterOptions, setCounterOptions] = useState({ counter: 0, resolve: undefined });

  useEffect(() => {
    counterOptions.resolve();
  }, [counterOptions]);

  return (
    <ReactToPrint
      onBeforeGetContent={() =>
        return new Promise((resolve) => {
          setCounterOptions(prevOptions => ({ counter: prevOptions.counter + 1, resolve }));
        });
      }
    />
  );
}

@MatthewHerbst Yes! That is what I want but I've not tested your code because many changes will happen in my code if I change my state structure (my state is array and not object). I solved it by hidding the ReactToPrint component and attaching a ref to it and trigger the print inside useEffect

let prescriptionPadRTPRef = React.createRef();

useEffect(() => {
     if (prescriptionPadRTPRef.current) {
          prescriptionPadRTPRef.current.handlePrint();
     }
}, [prescriptions]);

const handlePrescriptionPadPrint = () => {
   //perform prescriptions state change here
};

<Button
    size="small"
    type="primary"
    onClick={handlePrescriptionPadPrint}
>
    <Icon type="printer" /> Print
    Prescription
</Button>
<div style={{ display: "none" }}>
    <ReactToPrint
        ref={r => {
            prescriptionPadRTPRef.current = r;
        }}
        trigger={() => <button>print</button>}
        content={() =>
            prescriptionPadRef.current
        }                                            
    />
</div>

It would be nice if the print can trigger using state change like:

<ReactToPrint
    print={true|false}
/>

Hopefully to implent it in future.
Thank you very much!

@MatthewHerbst tested and confirmed your snippet worked great!
(one small fix is to make initialization of resolve a function)

  const [counterOptions, setCounterOptions] = useState({ counter: 0, resolve: () => undefined });

hello everyone,
can anybody help me, I am having issues with react to print. The problem is that when the UI changes and the state updates, when I hit the print ref button it still brings the old pdf with the privious data, no matter how many times I save and the state updates. the only work around to see the new print preview is if I reload the browser.

content={() => this.refPrintableArea.current} trigger={() => <button style={{display: 'none'}} ref={this.refPrintButton}>Print</button>}

@alejandroyunes could you share a codesandbox showing your code please?

I want some component that should be hidden in the start and when I click on the print button it should show and can be printable how do it?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hoangdoan267 picture hoangdoan267  路  8Comments

sprietNathanael picture sprietNathanael  路  4Comments

icebravo picture icebravo  路  5Comments

MikeSha picture MikeSha  路  4Comments

ishan7 picture ishan7  路  3Comments