0
# Context Hooks
1
2
React hooks for accessing PDF context data and state within component trees, enabling custom components to integrate with the react-pdf ecosystem.
3
4
## Capabilities
5
6
### useDocumentContext Hook
7
8
Provides access to document-level context data including PDF document proxy, link service, and page registration functions.
9
10
```typescript { .api }
11
/**
12
* Hook to access document context data
13
* Must be used within a Document component or provider
14
* @returns Document context object or null if outside Document
15
*/
16
function useDocumentContext(): DocumentContextType;
17
18
interface DocumentContextType {
19
/** Loaded PDF document proxy */
20
pdf?: PDFDocumentProxy | false;
21
/** Link service for handling internal and external links */
22
linkService: LinkService;
23
/** Function to register page elements for navigation */
24
registerPage: (pageIndex: number, ref: HTMLDivElement) => void;
25
/** Function to unregister page elements */
26
unregisterPage: (pageIndex: number) => void;
27
/** Item click handler for navigation */
28
onItemClick?: (args: OnItemClickArgs) => void;
29
/** Image resources path for annotations */
30
imageResourcesPath?: string;
31
/** Document rendering mode */
32
renderMode?: RenderMode;
33
/** Document scale factor */
34
scale?: number;
35
/** Document rotation */
36
rotate?: number | null;
37
} | null;
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { useDocumentContext } from "react-pdf";
44
45
// Custom component accessing document info
46
function DocumentInfo() {
47
const documentContext = useDocumentContext();
48
49
if (!documentContext?.pdf) {
50
return <div>No document loaded</div>;
51
}
52
53
const { pdf } = documentContext;
54
55
return (
56
<div className="document-info">
57
<h3>Document Information</h3>
58
<p>Total Pages: {pdf.numPages}</p>
59
<p>PDF.js Version: {pdf.pdfInfo?.PDFFormatVersion}</p>
60
</div>
61
);
62
}
63
64
// Custom navigation component
65
function CustomNavigation() {
66
const documentContext = useDocumentContext();
67
const [currentPage, setCurrentPage] = useState(1);
68
69
if (!documentContext?.pdf) return null;
70
71
const { pdf, onItemClick } = documentContext;
72
73
const handlePageChange = (pageNumber) => {
74
setCurrentPage(pageNumber);
75
if (onItemClick) {
76
onItemClick({
77
pageIndex: pageNumber - 1,
78
pageNumber: pageNumber
79
});
80
}
81
};
82
83
return (
84
<div className="custom-navigation">
85
<button
86
onClick={() => handlePageChange(Math.max(1, currentPage - 1))}
87
disabled={currentPage === 1}
88
>
89
Previous
90
</button>
91
<span>Page {currentPage} of {pdf.numPages}</span>
92
<button
93
onClick={() => handlePageChange(Math.min(pdf.numPages, currentPage + 1))}
94
disabled={currentPage === pdf.numPages}
95
>
96
Next
97
</button>
98
</div>
99
);
100
}
101
102
// Document metadata extractor
103
function DocumentMetadata() {
104
const documentContext = useDocumentContext();
105
const [metadata, setMetadata] = useState(null);
106
107
useEffect(() => {
108
if (documentContext?.pdf) {
109
documentContext.pdf.getMetadata().then(({ info, metadata }) => {
110
setMetadata({ info, metadata });
111
});
112
}
113
}, [documentContext?.pdf]);
114
115
if (!metadata) return <div>Loading metadata...</div>;
116
117
return (
118
<div className="metadata">
119
<h3>Document Metadata</h3>
120
<p>Title: {metadata.info.Title || 'Untitled'}</p>
121
<p>Author: {metadata.info.Author || 'Unknown'}</p>
122
<p>Subject: {metadata.info.Subject || 'None'}</p>
123
<p>Creator: {metadata.info.Creator || 'Unknown'}</p>
124
<p>Producer: {metadata.info.Producer || 'Unknown'}</p>
125
<p>Creation Date: {metadata.info.CreationDate || 'Unknown'}</p>
126
</div>
127
);
128
}
129
```
130
131
### usePageContext Hook
132
133
Provides access to page-level context data including page proxy, dimensions, and rendering settings.
134
135
```typescript { .api }
136
/**
137
* Hook to access page context data
138
* Must be used within a Page component or provider
139
* @returns Page context object or null if outside Page
140
*/
141
function usePageContext(): PageContextType;
142
143
interface PageContextType {
144
/** PDF page proxy object */
145
page: PDFPageProxy | false | undefined;
146
/** Zero-based page index */
147
pageIndex: number;
148
/** One-based page number */
149
pageNumber: number;
150
/** Page scale factor */
151
scale: number;
152
/** Page rotation in degrees */
153
rotate: number;
154
/** Canvas background color */
155
canvasBackground?: string;
156
/** Custom text renderer function */
157
customTextRenderer?: CustomTextRenderer;
158
/** Device pixel ratio */
159
devicePixelRatio?: number;
160
/** Whether forms should be rendered */
161
renderForms: boolean;
162
/** Whether text layer should be rendered */
163
renderTextLayer: boolean;
164
/** Internal CSS class name */
165
_className?: string;
166
} | null;
167
```
168
169
**Usage Examples:**
170
171
```typescript
172
import { usePageContext } from "react-pdf";
173
174
// Custom page overlay component
175
function PageOverlay() {
176
const pageContext = usePageContext();
177
178
if (!pageContext?.page) return null;
179
180
const { page, pageNumber, scale, rotate } = pageContext;
181
const viewport = page.getViewport({ scale, rotation: rotate });
182
183
return (
184
<div
185
className="page-overlay"
186
style={{
187
position: 'absolute',
188
top: 0,
189
left: 0,
190
width: viewport.width,
191
height: viewport.height,
192
pointerEvents: 'none',
193
border: '2px solid red',
194
zIndex: 10
195
}}
196
>
197
<div className="page-label">
198
Page {pageNumber}
199
</div>
200
</div>
201
);
202
}
203
204
// Page dimensions display
205
function PageDimensions() {
206
const pageContext = usePageContext();
207
208
if (!pageContext?.page) return null;
209
210
const { page, scale, rotate } = pageContext;
211
const viewport = page.getViewport({ scale: 1, rotation: rotate });
212
const scaledViewport = page.getViewport({ scale, rotation: rotate });
213
214
return (
215
<div className="page-dimensions">
216
<h4>Page Dimensions</h4>
217
<p>Original: {viewport.width.toFixed(1)} × {viewport.height.toFixed(1)}</p>
218
<p>Scaled: {scaledViewport.width.toFixed(1)} × {scaledViewport.height.toFixed(1)}</p>
219
<p>Scale: {(scale * 100).toFixed(0)}%</p>
220
<p>Rotation: {rotate}°</p>
221
</div>
222
);
223
}
224
225
// Custom text highlighter using page context
226
function TextHighlighter({ searchTerm }) {
227
const pageContext = usePageContext();
228
const [textItems, setTextItems] = useState([]);
229
230
useEffect(() => {
231
if (pageContext?.page) {
232
pageContext.page.getTextContent().then(textContent => {
233
setTextItems(textContent.items);
234
});
235
}
236
}, [pageContext?.page]);
237
238
if (!pageContext?.page || !searchTerm) return null;
239
240
const { page, scale, rotate } = pageContext;
241
const viewport = page.getViewport({ scale, rotation: rotate });
242
243
return (
244
<div className="text-highlights">
245
{textItems
246
.filter(item => item.str.toLowerCase().includes(searchTerm.toLowerCase()))
247
.map((item, index) => {
248
const [x, y, width, height] = item.transform;
249
return (
250
<div
251
key={index}
252
className="highlight"
253
style={{
254
position: 'absolute',
255
left: x * scale,
256
top: viewport.height - (y * scale),
257
width: item.width * scale,
258
height: item.height * scale,
259
backgroundColor: 'yellow',
260
opacity: 0.3,
261
pointerEvents: 'none'
262
}}
263
/>
264
);
265
})}
266
</div>
267
);
268
}
269
```
270
271
### useOutlineContext Hook
272
273
Provides access to outline-specific context data for navigation and interaction handling.
274
275
```typescript { .api }
276
/**
277
* Hook to access outline context data
278
* Must be used within an Outline component or provider
279
* @returns Outline context object or null if outside Outline
280
*/
281
function useOutlineContext(): OutlineContextType;
282
283
interface OutlineContextType {
284
/** Item click handler for outline navigation */
285
onItemClick?: (args: OnItemClickArgs) => void;
286
} | null;
287
```
288
289
**Usage Examples:**
290
291
```typescript
292
import { useOutlineContext } from "react-pdf";
293
294
// Custom outline item component
295
function CustomOutlineItem({ item, level = 0 }) {
296
const outlineContext = useOutlineContext();
297
298
const handleClick = () => {
299
if (outlineContext?.onItemClick && item.dest) {
300
// Note: In real implementation, you'd need to resolve dest to page number
301
outlineContext.onItemClick({
302
dest: item.dest,
303
pageIndex: 0, // Would need to resolve from dest
304
pageNumber: 1 // Would need to resolve from dest
305
});
306
}
307
};
308
309
return (
310
<div className="custom-outline-item">
311
<div
312
className={`outline-title level-${level}`}
313
onClick={handleClick}
314
style={{
315
paddingLeft: level * 20,
316
cursor: 'pointer',
317
fontWeight: item.bold ? 'bold' : 'normal',
318
fontStyle: item.italic ? 'italic' : 'normal',
319
color: item.color ? `rgb(${item.color.join(',')})` : 'inherit'
320
}}
321
>
322
{item.title}
323
</div>
324
{item.items && item.items.length > 0 && (
325
<div className="outline-children">
326
{item.items.map((child, index) => (
327
<CustomOutlineItem
328
key={index}
329
item={child}
330
level={level + 1}
331
/>
332
))}
333
</div>
334
)}
335
</div>
336
);
337
}
338
339
// Outline with custom interaction
340
function InteractiveOutline() {
341
const outlineContext = useOutlineContext();
342
const [expandedItems, setExpandedItems] = useState(new Set());
343
344
const toggleExpanded = (itemId) => {
345
const newExpanded = new Set(expandedItems);
346
if (newExpanded.has(itemId)) {
347
newExpanded.delete(itemId);
348
} else {
349
newExpanded.add(itemId);
350
}
351
setExpandedItems(newExpanded);
352
};
353
354
return (
355
<div className="interactive-outline">
356
{/* Custom outline rendering with expand/collapse */}
357
</div>
358
);
359
}
360
```
361
362
### Combined Context Usage
363
364
Using multiple context hooks together for complex custom components.
365
366
```typescript { .api }
367
interface CombinedContextUsage {
368
/** Using document and page contexts together */
369
documentAndPageContext?: () => void;
370
/** Using all available contexts */
371
allContexts?: () => void;
372
}
373
```
374
375
**Usage Examples:**
376
377
```typescript
378
// Component using both document and page contexts
379
function PageWithDocumentInfo() {
380
const documentContext = useDocumentContext();
381
const pageContext = usePageContext();
382
383
if (!documentContext?.pdf || !pageContext?.page) {
384
return <div>Loading...</div>;
385
}
386
387
const { pdf } = documentContext;
388
const { pageNumber, scale } = pageContext;
389
390
return (
391
<div className="page-with-info">
392
<div className="page-header">
393
<h3>Document: {pdf.numPages} pages</h3>
394
<p>Current: Page {pageNumber} at {(scale * 100).toFixed(0)}%</p>
395
</div>
396
{/* Page content would be rendered here */}
397
</div>
398
);
399
}
400
401
// Full-featured PDF viewer using all contexts
402
function FullFeaturedViewer() {
403
const documentContext = useDocumentContext();
404
const pageContext = usePageContext();
405
const outlineContext = useOutlineContext();
406
407
const [showMetadata, setShowMetadata] = useState(false);
408
const [showOutline, setShowOutline] = useState(false);
409
410
return (
411
<div className="full-viewer">
412
<div className="toolbar">
413
<button onClick={() => setShowMetadata(!showMetadata)}>
414
{showMetadata ? 'Hide' : 'Show'} Metadata
415
</button>
416
<button onClick={() => setShowOutline(!showOutline)}>
417
{showOutline ? 'Hide' : 'Show'} Outline
418
</button>
419
420
{pageContext && (
421
<span>
422
Page {pageContext.pageNumber} •
423
Scale {(pageContext.scale * 100).toFixed(0)}%
424
</span>
425
)}
426
</div>
427
428
<div className="viewer-content">
429
{showOutline && (
430
<div className="sidebar">
431
{/* Custom outline component */}
432
</div>
433
)}
434
435
<div className="main-content">
436
{showMetadata && (
437
<div className="metadata-panel">
438
{/* Document metadata */}
439
</div>
440
)}
441
{/* Page content */}
442
</div>
443
</div>
444
</div>
445
);
446
}
447
448
// Context validation helper
449
function useValidatedContexts() {
450
const documentContext = useDocumentContext();
451
const pageContext = usePageContext();
452
453
const isDocumentReady = Boolean(documentContext?.pdf);
454
const isPageReady = Boolean(pageContext?.page);
455
456
return {
457
documentContext,
458
pageContext,
459
isDocumentReady,
460
isPageReady,
461
isFullyReady: isDocumentReady && isPageReady
462
};
463
}
464
465
function ContextAwareComponent() {
466
const {
467
documentContext,
468
pageContext,
469
isFullyReady
470
} = useValidatedContexts();
471
472
if (!isFullyReady) {
473
return <div>Contexts not ready</div>;
474
}
475
476
return (
477
<div>
478
{/* Component that requires both contexts */}
479
</div>
480
);
481
}
482
```
483
484
### Context Error Handling
485
486
Proper error handling and validation when using context hooks.
487
488
```typescript { .api }
489
interface ContextErrorHandling {
490
/** Validate context availability */
491
validateContext?: () => boolean;
492
/** Handle missing context gracefully */
493
handleMissingContext?: () => React.ReactElement;
494
}
495
```
496
497
**Usage Examples:**
498
499
```typescript
500
// Context validation wrapper
501
function withContextValidation(Component, requiredContexts = []) {
502
return function ValidatedComponent(props) {
503
const documentContext = useDocumentContext();
504
const pageContext = usePageContext();
505
const outlineContext = useOutlineContext();
506
507
const contexts = {
508
document: documentContext,
509
page: pageContext,
510
outline: outlineContext
511
};
512
513
// Check if all required contexts are available
514
const missingContexts = requiredContexts.filter(
515
contextName => !contexts[contextName]
516
);
517
518
if (missingContexts.length > 0) {
519
console.warn(
520
`Component requires ${requiredContexts.join(', ')} context(s), ` +
521
`but ${missingContexts.join(', ')} ${missingContexts.length === 1 ? 'is' : 'are'} missing`
522
);
523
return <div>Missing required PDF context</div>;
524
}
525
526
return <Component {...props} />;
527
};
528
}
529
530
// Usage
531
const ValidatedPageInfo = withContextValidation(
532
function PageInfo() {
533
const pageContext = usePageContext();
534
return <div>Page {pageContext.pageNumber}</div>;
535
},
536
['page']
537
);
538
539
// Safe context hook with fallbacks
540
function useSafeDocumentContext() {
541
const context = useDocumentContext();
542
543
return {
544
pdf: context?.pdf || null,
545
numPages: context?.pdf?.numPages || 0,
546
isReady: Boolean(context?.pdf),
547
linkService: context?.linkService || null,
548
registerPage: context?.registerPage || (() => {}),
549
unregisterPage: context?.unregisterPage || (() => {})
550
};
551
}
552
553
function SafeDocumentInfo() {
554
const { pdf, numPages, isReady } = useSafeDocumentContext();
555
556
if (!isReady) {
557
return <div>Document not ready</div>;
558
}
559
560
return <div>Document has {numPages} pages</div>;
561
}
562
```