Skip to content

Commit 811614c

Browse files
committed
Add support for render props in Document and Page components
1 parent 7b6b2d3 commit 811614c

File tree

5 files changed

+83
-7
lines changed

5 files changed

+83
-7
lines changed

packages/react-pdf/src/Document.spec.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeAll, describe, expect, it, vi } from 'vitest';
2-
import { fireEvent, getByTestId, render } from '@testing-library/react';
2+
import { fireEvent, getByTestId, getByText, render } from '@testing-library/react';
33
import { createRef } from 'react';
44

55
import Document from './Document.js';
@@ -464,6 +464,24 @@ describe('Document', () => {
464464

465465
expect(child.dataset.scale).toBe('2');
466466
});
467+
468+
it('supports function as children', async () => {
469+
const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback();
470+
471+
const { container } = render(
472+
<Document file={pdfFile.file} loading="Loading" onLoadSuccess={onLoadSuccess}>
473+
{({ pdf }) => <p>{`This PDF has ${pdf.numPages} pages`}</p>}
474+
</Document>,
475+
);
476+
477+
expect.assertions(1);
478+
479+
await onLoadSuccessPromise;
480+
481+
const child = getByText(container, 'This PDF has 4 pages');
482+
483+
expect(child).toBeInTheDocument();
484+
});
467485
});
468486

469487
describe('viewer', () => {

packages/react-pdf/src/Document.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api.js
3333
import type {
3434
ClassName,
3535
DocumentCallback,
36+
DocumentContextType,
3637
ExternalLinkRel,
3738
ExternalLinkTarget,
3839
File,
@@ -46,6 +47,7 @@ import type {
4647
OnPasswordCallback,
4748
Options,
4849
PasswordResponse,
50+
DocumentRenderProps,
4951
RenderMode,
5052
ScrollPageIntoViewArgs,
5153
Source,
@@ -62,7 +64,7 @@ type OnSourceError = OnError;
6264
type OnSourceSuccess = () => void;
6365

6466
export type DocumentProps = {
65-
children?: React.ReactNode;
67+
children?: React.ReactNode | ((props: DocumentRenderProps) => React.ReactNode);
6668
/**
6769
* Class name(s) that will be added to rendered element along with the default `react-pdf__Document`.
6870
*
@@ -600,7 +602,20 @@ const Document: React.ForwardRefExoticComponent<
600602
);
601603

602604
function renderChildren() {
603-
return <DocumentContext.Provider value={childContext}>{children}</DocumentContext.Provider>;
605+
function isFulfilledContext(context: DocumentContextType): context is DocumentRenderProps {
606+
return Boolean(context?.pdf);
607+
}
608+
609+
if (!isFulfilledContext(childContext)) {
610+
// Impossible, but TypeScript doesn't know that
611+
throw new Error('pdf is undefined');
612+
}
613+
614+
const resolvedChildren = typeof children === 'function' ? children(childContext) : children;
615+
616+
return (
617+
<DocumentContext.Provider value={childContext}>{resolvedChildren}</DocumentContext.Provider>
618+
);
604619
}
605620

606621
function renderContent() {

packages/react-pdf/src/Page.spec.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeAll, describe, expect, it, vi } from 'vitest';
2-
import { fireEvent, render } from '@testing-library/react';
2+
import { fireEvent, getByText, render } from '@testing-library/react';
33
import { createRef } from 'react';
44

55
import DocumentContext from './DocumentContext.js';
@@ -732,6 +732,28 @@ describe('Page', () => {
732732

733733
expect(annotationLayer).not.toBeInTheDocument();
734734
});
735+
736+
it('supports function as children', async () => {
737+
const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback();
738+
739+
const { container } = renderWithContext(
740+
<Page onLoadSuccess={onLoadSuccess} pageIndex={0}>
741+
{({ page }) => <p>{`Page ${page.pageNumber}`}</p>}
742+
</Page>,
743+
{
744+
linkService,
745+
pdf,
746+
},
747+
);
748+
749+
expect.assertions(1);
750+
751+
await onLoadSuccessPromise;
752+
753+
const child = getByText(container, 'Page 1');
754+
755+
expect(child).toBeInTheDocument();
756+
});
735757
});
736758

737759
it('requests page to be rendered without forms by default', async () => {

packages/react-pdf/src/Page.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import type {
4141
OnRenderTextLayerError,
4242
OnRenderTextLayerSuccess,
4343
PageCallback,
44+
PageContextType,
45+
PageRenderProps,
4446
RenderMode,
4547
} from './shared/types.js';
4648

@@ -63,7 +65,7 @@ export type PageProps = {
6365
* @example ref
6466
*/
6567
canvasRef?: React.Ref<HTMLCanvasElement>;
66-
children?: React.ReactNode;
68+
children?: React.ReactNode | ((props: PageRenderProps) => React.ReactNode);
6769
/**
6870
* Class name(s) that will be added to rendered element along with the default `react-pdf__Page`.
6971
*
@@ -489,7 +491,7 @@ export default function Page(props: PageProps): React.ReactElement {
489491
const childContext = useMemo(
490492
() =>
491493
// Technically there cannot be page without pageIndex, pageNumber, rotate and scale, but TypeScript doesn't know that
492-
page && isProvided(pageIndex) && pageNumber && isProvided(rotate) && isProvided(scale)
494+
isProvided(pageIndex) && pageNumber && isProvided(rotate) && isProvided(scale)
493495
? {
494496
_className,
495497
canvasBackground,
@@ -589,12 +591,23 @@ export default function Page(props: PageProps): React.ReactElement {
589591
}
590592

591593
function renderChildren() {
594+
function isFulfilledContext(context: PageContextType): context is PageRenderProps {
595+
return Boolean(context?.page);
596+
}
597+
598+
if (!isFulfilledContext(childContext)) {
599+
// Impossible, but TypeScript doesn't know that
600+
throw new Error('page is undefined');
601+
}
602+
603+
const resolvedChildren = typeof children === 'function' ? children(childContext) : children;
604+
592605
return (
593606
<PageContext.Provider value={childContext}>
594607
{renderMainLayer()}
595608
{renderTextLayer()}
596609
{renderAnnotationLayer()}
597-
{children}
610+
{resolvedChildren}
598611
</PageContext.Provider>
599612
);
600613
}

packages/react-pdf/src/shared/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,11 @@ export type StructTreeNodeWithExtraAttributes = StructTreeNode & {
175175
alt?: string;
176176
lang?: string;
177177
};
178+
179+
export type DocumentRenderProps = Omit<NonNullable<DocumentContextType>, 'pdf'> & {
180+
pdf: PDFDocumentProxy;
181+
};
182+
183+
export type PageRenderProps = Omit<NonNullable<PageContextType>, 'page'> & {
184+
page: PDFPageProxy;
185+
};

0 commit comments

Comments
 (0)