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

3.0.0 upgrade help #742

Open
Lokray opened this issue Sep 30, 2024 · 20 comments
Open

3.0.0 upgrade help #742

Lokray opened this issue Sep 30, 2024 · 20 comments

Comments

@Lokray
Copy link

Lokray commented Sep 30, 2024

Hi after updateing to 3.0.0 I get the error "t is not a function", even after breaking it down to your example:

Print
Content to print

I used chrome on windows.
Please forgive me if this is the wrong place or I am missing info, it's the first time for posting an issue ;)

@MariusMez
Copy link

MariusMez commented Sep 30, 2024

Hi, same error on my side after upgrade to v3 :

Before:

const ficheToPrintRef = useRef(null);
const handlePrint = useReactToPrint({
    content: () => ficheToPrintRef.current,
});

After my comprehension of the changelog:

const ficheToPrintRef = useRef<HTMLDivElement>(null);
const handlePrint = useReactToPrint({ contentRef: ficheToPrintRef });

But when calling handlePrint I have this error too: "react-to-print.js?v=05cacf31:163 Uncaught TypeError: t3 is not a function" and also a warning log on the console :

"[
    "\"react-to-print\" received a `contentRef` option and a optional-content param passed to its callback. The `contentRef` option will be ignored."
]"

edit : lines where the error pop:

function g(e2) {
        const { contentRef: t2, fonts: o2, ignoreGlobalStyles: n2, onBeforePrint: r2, onPrintError: c2, preserveAfterPrint: d2, suppressErrors: u2 } = e2, p2 = (0, s.useCallback)((s2) => {
          l(d2, true);
          const p3 = function({ contentRef: e3, optionalContent: t3, suppressErrors: o3 }) {
            return t3 ? (e3 && i({ level: "warning", messages: ['"react-to-print" received a `contentRef` option and a optional-content param passed to its callback. The `contentRef` option will be ignored.'] }), t3()) : e3 ? e3.current : void i({ messages: ['"react-to-print" did not receive a `contentRef` option or a optional-content param pass to its callback.'], suppressErrors: o3 });
          }({ contentRef: t2, optionalContent: s2, suppressErrors: u2 });
          if (!p3) return void i({ messages: ["There is nothing to print"], suppressErrors: u2 });
          if (!p3) return void i({ messages: ['"react-to-print" could not locate the DOM node corresponding with the `content` prop'], suppressErrors: u2 });
          const g2 = p3.cloneNode(true), f = document.querySelectorAll("link[rel~='stylesheet'], link[as='style']"), m = g2.querySelectorAll("img"), b = g2.querySelectorAll("video"), y = o2 ? o2.length : 0, v = (n2 ? 0 : f.length) + m.length + b.length + y, w = [], E = [], T = function() {
            const e3 = document.createElement("iframe");
            return e3.width = `${document.documentElement.clientWidth}px`, e3.height = `${document.documentElement.clientHeight}px`, e3.style.position = "absolute", e3.style.top = `-${document.documentElement.clientHeight + 100}px`, e3.style.left = `-${document.documentElement.clientWidth + 100}px`, e3.id = "printWindow", e3.srcdoc = "<!DOCTYPE html>", e3;
          }(), A = (t3, o3) => {
            w.includes(t3) ? i({ level: "debug", messages: ["Tried to mark a resource that has already been handled", t3], suppressErrors: u2 }) : (o3 ? (i({ messages: ['"react-to-print" was unable to load a resource but will continue attempting to print the page', ...o3], suppressErrors: u2 }), E.push(t3)) : w.push(t3), w.length + E.length === v && a(T, e2));
          }, x = { contentNode: p3, clonedContentNode: g2, clonedImgNodes: m, clonedVideoNodes: b, numResourcesToLoad: v, originalCanvasNodes: p3.querySelectorAll("canvas") };
          r2 ? r2().then(() => h(T, A, x, e2)).catch((e3) => {
            null == c2 || c2("onBeforePrint", e3);
          }) : h(T, A, x, e2);
        }, [e2]);
        return p2;
      }

@MariusMez
Copy link

MariusMez commented Sep 30, 2024

Finally problem solved after seeing this issue : #724

My previous code was calling handlePrint directly: onClick={handlePrint} and now it works only with onClick={() => handlePrint()}

@Lokray
Copy link
Author

Lokray commented Sep 30, 2024

Thanks! My research didn't leed to this. But this leeds to a next question, I can't find how the documentTitle is now set.
Previously i set it like

    const handlePrint = useReactToPrint({
        documentTitle: 'Title',
        content: () => componentRef.current,

    });

But if I am passing the ref like that, the console says nothing to print

@MariusMez
Copy link

MariusMez commented Sep 30, 2024

Try:

const handlePrint = useReactToPrint({
        documentTitle: 'Title',
        contentRef: componentRef,
    });

@MatthewHerbst
Copy link
Owner

Hey folks, thanks for the messages, apologies if the upgrade process hasn't been smooth. Please feel free to continue posting in this thread and I'll answer/help as best I can. The core change as seen above is a new pattern for passing in the componentRef which allows us to be React 19 compatible

@MatthewHerbst MatthewHerbst changed the title t is not a function 3.0.0 upgrade help Sep 30, 2024
@Idnan
Copy link

Idnan commented Sep 30, 2024

Hi guys, I am still getting the same error after upgrading to v3. I tried all the above solutions but none worked. Here's how my code looks like after update.

import { Box, Flex, HStack } from '@chakra-ui/react';
import { BaseButton } from '@components/base/base-button';
import { useRef } from 'react';
import { useReactToPrint } from 'react-to-print';
import { TrayListTablePrint } from './tray-list-table-print';

interface TableActionProps {
  onPrint?: () => void;
}

function TableActions({ onPrint }: TableActionProps) {
  return (
    <Flex direction='column'>
      <Flex justifyContent='flex-end' mb={4}>
        <HStack spacing='sm'>
          <BaseButton size='sm' onClick={onPrint} colorScheme='green'>
            Print
          </BaseButton>
        </HStack>
      </Flex>
    </Flex>
  );
}

export function TrayListTable() {
  const componentToPrintRef = useRef(null);

  const handlePrint = useReactToPrint({
    contentRef: componentToPrintRef
  });

  return (
    <Box minH='100%' pos='relative'>
      <TableActions
        onPrint={handlePrint}
      />
      <Box style={{ display: 'none' }}>
        <TrayListTablePrint ref={componentToPrintRef} />
      </Box>
    </Box>
  );
}

@MatthewHerbst
Copy link
Owner

@Idnan could you please be more specific about the error you are seeing?

@Idnan
Copy link

Idnan commented Sep 30, 2024

I am getting this error TypeError: t is not a function and I am using nextjs v14.2.13 with typescript v5.6.2.

image

Here's how the old code looked like

const componentToPrintRef = useRef(null);

const reactToPrintContent = useCallback(() => {
  return componentToPrintRef.current;
}, [componentToPrintRef.current]);

const handlePrint = useReactToPrint({
  content: reactToPrintContent
});

And after upgrading the react-to-print to v3. I modified the above code to this, and started seeing the above error.

const componentToPrintRef = useRef(null);

const handlePrint = useReactToPrint({
  contentRef: componentToPrintRef
});

@MatthewHerbst
Copy link
Owner

I see the source of confusion, I will push a doc update right now. @Idnan changing onPrint={handlePrint} to onPrint={() => handlePrint()} should fix your issue

@Idnan
Copy link

Idnan commented Sep 30, 2024

Thanks @MatthewHerbst for the quick help. I just tried and now it's giving me a new error

TypeError: p.cloneNode is not a function

@MatthewHerbst
Copy link
Owner

MatthewHerbst commented Sep 30, 2024

I've published v3.0.1 which should resolve the t is not a function issue. I expect that this primarily impacts non-TypeScript users, since folks using TypeScript will see a type error if they try to pass the print fn directly in v3

@Idnan the cloneNode is not a function error is interesting. Could you please share what browser you're using? cloneNode should be supported by all major browsers

@Idnan
Copy link

Idnan commented Sep 30, 2024

Thanks I will upgrade the package to v3.0.1 and test it.

Regarding the cloneNode I am using chrome 129.0 which is the latest version.

@Idnan
Copy link

Idnan commented Sep 30, 2024

I have figured out the issue with the cloneNode.

Here's the old code.

<Box style={{ display: 'none' }}>
  <TrayListTablePrint ref={componentToPrintRef} columns={columnsToPrint} data={list} />
</Box>

I think the issue was with the ref. So I wrapped my component TrayListTablePrint in a div and assigned the ref to it. And it fixed the issue.
Here's how the new code looks like

<Box style={{ display: 'none' }}>
  <div ref={componentToPrintRef}>
    <TrayListTablePrint columns={columnsToPrint} data={list} />
  </div>
</Box>

Thanks for the help @MatthewHerbst

@MatthewHerbst
Copy link
Owner

MatthewHerbst commented Sep 30, 2024

Glad you got it figured out! If your component is a functional component you'll need to wrap it in forwardRef which should remove the need for the wrapping div

@hemm1
Copy link

hemm1 commented Oct 2, 2024

Hi! We tried to update to the latest version today and ran into an issue. Earlier, we used onBeforeGetContent and a combination of two different refs to be able to load some data and wait for everything to load and render completely before we actually initiated the printing. You helped me a bit with the setup here: #469

Now, we are trying to use onBeforePrint and contentRef to accomplish the same. Our contentRef is null when we initially start the printing, but onBeforePrint is responsible for starting the data fetching and rendering and eventually setting the contentRef to the actual node we want to print.

We ran into two issues:

  1. If contentRef is null when handlePrint is first called, the function returns with an error 'There is nothing to print' before even calling the onBeforePrint method. This was not the case in the earlier version.
  2. To get around this problem, we tried adding a different, placeholder component as the contentRef in order to make the onBeforePrint run, but we could not get the timing right for the contentRef to actually be updated when the element was printed. We had to try twice for each print in order to make it work.

I understand that our setup is a bit special, but it worked like a charm in the previous version. Any pointers at all for how to make it work in version 3 would be greatly appreciated. I can also, of course, post some code or make a separate issue if that is better.

@MatthewHerbst
Copy link
Owner

Hey @hemm1 nice to hear from you again! I believe you should be able to solve your workflow by using the lazy print option we added in v2.15.0. Basically, you can now delay calling the callback from useReactToPrint until you have the content ready. The LazyContent example should point you in the right direction. Please let me know if that doesn't work for you, or if you have trouble adopting it.

@hemm1
Copy link

hemm1 commented Oct 3, 2024

Thank you very much! This looks to be right up our alley. Most likely we should have been using Lazy print already, but we did not now it was added until now 😇 We will definitely try it out and get back to you 👌

@hemm1
Copy link

hemm1 commented Oct 3, 2024

Hi again! Me and my colleague tinkered a bit with this today, and we could not get the LazyContent to work for us either, unfortunately. The problem was the same, in that the contentRef is null when handlePrint is called. So the onBeforePrint function never gets called.

Our problem is that the component we want to print does not exist (nor the data for it) when we click the button to print it. When we click the print button, we then first want to do a couple data calls, render the component and then finally print the component.

We did manage to find a way to do it which seems to work perfectly, however. Instead of invoking react-to-print at the beginning of the process and then needing react-to-print to orchestrate everything and wait for the process to finish, we tried to make the component render without involving react-to-print and then actually invoking handlePrint when everything is ready to be printed. This removes the need for the "manual" Promise resolving and indeed any refs other than one useCallback and actually makes the code easier in our case.

A quick code example is in order:

const [isPrinting, setIsPrinting] = useState(false);

const handlePrint = useReactToPrint({
  onAfterPrint: () => setIsPrinting(false),
});

const callbackRef = useCallback(
  (node: HTMLDivElement) => {
    if (node !== null) handlePrint(() => node);
  },
  [handlePrint]
);

return (
  <div className={c.link} onClick={() => setIsPrinting(true)}>
    <div style={{ display: 'none' }}>
      {isPrinting && (
        <React.Suspense fallback={null}>
          <PrintablePriceBoard id={car.id} salesForm={car.salesForm} ref={callbackRef} />
        </React.Suspense>
      )}
    </div>
  </div>

Inside the PrintablePriceBoard component we trigger data fetching and wait until all data is fetched before we render anything. And the containing div of the component gets passed the ref in the normal way like so : <div ref={ref}>.

We can't see any problems with this and are very happy with how it all turned out. 🎉

Thank you again for an awesome library and very attentive and helpful comments! 🙌

@MatthewHerbst
Copy link
Owner

@hemm1 that looks great, thank you so much for sharing, callback ref makes perfect sense! I'll look into how to best update the README and examples to show this method

@eugene-khyst
Copy link

eugene-khyst commented Nov 4, 2024

Based on @hemm1 answer I came up with the following generic component PrintableImages that prints lazy loaded images:

interface Props {
  images: () => Promise<Blob[]>;
  printing: boolean;
  setPrinting: React.Dispatch<React.SetStateAction<boolean>>;
}

export const PrintableImages: React.FC<Props> = ({images, printing, setPrinting}: Props) => {
  const [printImagesUrls, setPrintImagesUrls] = useState<string[]>([]);

  const printFn = useReactToPrint({
    onAfterPrint: () => {
      setPrintImagesUrls(prev => {
        prev.forEach((url: string) => {
          URL.revokeObjectURL(url);
        });
        return [];
      });
      setPrinting(false);
    },
  });

  const printRef = useCallback(
    (node: HTMLDivElement | null) => {
      if (node) {
        printFn(() => node);
      }
    },
    [printFn]
  );

  useEffect(() => {
    if (!printing) {
      return;
    }
    void (async () => {
      const urls: string[] = (await images).map((blob: Blob): string => URL.createObjectURL(blob));
      setPrintImagesUrls(urls);
    })();
  }, [image, printing]);

  return (
    <div style={{display: 'none'}}>
      {!!printImagesUrls.length && (
        <div ref={printRef}>
          {printImagesUrls.map((url: string, i: number) => (
            <img key={i} src={url} style={{breakAfter: 'always'}} />
          ))}
        </div>
      )}
    </div>
  );
};

Example usage:

const [isPrinting, setIsPrinting] = useState<boolean>(false);
//...
<Button onClick={() => {setIsPrinting(true)}}>Print</Button>
<PrintableImages image={images} printing={isPrinting} setPrinting={setIsPrinting} />

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

No branches or pull requests

6 participants