or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-hooks.mddocument-management.mdindex.mdnavigation-components.mdpage-rendering.md

context-hooks.mddocs/

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

```