Pdf viewer with page thumbnails, page navigation, and zooming capabilities for React Native apps.
npm install @thatkid02/react-native-pdf-viewer

High-performance PDF viewer for React Native, built with Nitro Modules for native rendering.

- Ask AI here - https://deepwiki.com/thatkid02/react-native-pdf-viewer
- 📄 Native PDF rendering — PDFKit (iOS) & PdfRenderer (Android)
- 🔍 Pinch-to-zoom & pan — Smooth gesture handling
- 🖼️ Thumbnail generation — Async page previews
- 🌐 Local & remote files — file://, http://, https://
- 📱 iOS layouts — Horizontal scroll & paging modes
- 💾 Memory efficient — Virtualized pages, LRU cache
``sh`
npm install @thatkid02/react-native-pdf-viewer react-native-nitro-modules
`sh`
cd ios && pod install
If you encounter issues during installation related to missing nitrogen/generated files, you may need to apply a yarn patch.
Problem: builder bob creates module folder for index with path issue
Solution: Add a yarn patch to lib/module/index.js to handle the missing nitrogen path:
`bash`
yarn patch @thatkid02/react-native-pdf-viewer
Then for patch modify lib/module/index.js and change this line:
`js
// Before (may fail if nitrogen not generated yet)
const PdfViewerConfig = require('../nitrogen/generated/shared/json/PdfViewerConfig.json');
// After (with proper path resolution)
const PdfViewerConfig = require('../../nitrogen/generated/shared/json/PdfViewerConfig.json');
`
Key point: The path ../../nitrogen is important because the nitrogen module exists at the repository root, not in the module directory.
After patching, run:
`bash`
yarn patch-commit -u
`tsx
import { PdfViewerView } from '@thatkid02/react-native-pdf-viewer';
function App() {
return (
style={{ flex: 1 }}
onLoadComplete={(e) => console.log(Loaded ${e.pageCount} pages)}`
onError={(e) => console.error(e.message)}
/>
);
}
Callbacks must be wrapped with callback() from nitro-modules to avoid re-renders. Learn more
`tsx
import { useRef } from 'react';
import { View, Button } from 'react-native';
import {
PdfViewerView,
callback,
type PdfViewerRef
} from '@thatkid02/react-native-pdf-viewer';
function PdfScreen() {
const pdfRef = useRef
return (
source="https://example.com/document.pdf"
style={{ flex: 1 }}
enableZoom={true}
minScale={0.5}
maxScale={4.0}
onLoadComplete={callback((e) => {
console.log(${e.pageCount} pages, ${e.pageWidth}x${e.pageHeight});Page ${e.page} of ${e.pageCount}
})}
onPageChange={callback((e) => {
console.log();Thumbnail page ${e.page}: ${e.uri}
})}
onThumbnailGenerated={callback((e) => {
console.log();
})}
/>
);
}
`
`tsx
// Horizontal scrolling
// Paging mode (swipe between pages)
// Combined
`
Content scrolls behind transparent headers/toolbars:
`tsx`
contentInsetTop={100} // Header height
contentInsetBottom={80} // Toolbar height
style={{ flex: 1 }}
/>
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| source | string | — | PDF URI (file://, http://, https://) |spacing
| | number | 8 | Space between pages (px) |enableZoom
| | boolean | true | Enable pinch-to-zoom |minScale
| | number | 0.5 | Minimum zoom scale |maxScale
| | number | 4.0 | Maximum zoom scale |showsActivityIndicator
| | boolean | true | Show loading spinner |horizontal
| | boolean | false | Horizontal scroll (iOS only) |enablePaging
| | boolean | false | Paging mode (iOS only) |contentInsetTop
| | number | 0 | Top inset for glass UI |contentInsetBottom
| | number | 0 | Bottom inset for glass UI |contentInsetLeft
| | number | 0 | Left inset |contentInsetRight
| | number | 0 | Right inset |
| Event | Payload | Description |
|-------|---------|-------------|
| onLoadComplete | { pageCount, pageWidth, pageHeight } | PDF loaded successfully |onPageChange
| | { page, pageCount } | Visible page changed |onScaleChange
| | { scale } | Zoom level changed |onError
| | { message, code } | Error occurred |onThumbnailGenerated
| | { page, uri } | Thumbnail ready |onLoadingChange
| | { isLoading } | Loading state changed |
`tsx
const pdfRef = useRef
// Navigate to page (0-indexed)
pdfRef.current?.goToPage(0);
// Set zoom level
pdfRef.current?.setScale(2.0);
// Generate single thumbnail
pdfRef.current?.generateThumbnail(0);
// Generate all thumbnails
pdfRef.current?.generateAllThumbnails();
// Get document info
const info = pdfRef.current?.getDocumentInfo();
// { pageCount, pageWidth, pageHeight, currentPage }
`
| Feature | Android | iOS |
|---------|:-------:|:---:|
| Vertical scroll | ✅ | ✅ |
| Horizontal scroll | — | ✅ |
| Paging mode | — | ✅ |
| Pinch-to-zoom | ✅ | ✅ |
| Double-tap zoom | ✅ | ✅ |
| Pan when zoomed | ✅ | ✅ |
| Thumbnails | ✅ | ✅ |
| Remote URLs | ✅ | ✅ |
| Local files | ✅ | ✅ |
Thumbnails are automatically cached on disk and in memory. When you call generateThumbnail():
1. Memory cache is checked first (instant)
2. Disk cache is checked next (fast)
3. Generated on-demand if not cached (async)
This means multiple PdfViewerView instances with the same URL share cached thumbnails:
`tsx
// Main viewer
hybridRef={callback((ref) => { mainRef.current = ref; })}
onThumbnailGenerated={callback((e) => {
// Thumbnail generated by main viewer is cached
setThumbnails(prev => new Map(prev).set(e.page, e.uri));
})}
/>
// Carousel - can request thumbnails even before its PDF loads
// Will return cached thumbnails instantly if main viewer already generated them
hybridRef={callback((ref) => {
// Request thumbnail - returns from cache if available
ref?.generateThumbnail(0);
})}
onThumbnailGenerated={callback((e) => {
setCarouselThumbnail(e.uri);
})}
/>
`
Note: If generateThumbnail() is called before the document loads, the request is queued and processed automatically once loading completes.
PDF fails to load from URL
Ensure the URL is accessible. Use HTTPS in production.
Out of memory on large PDFs
Lower maxScale` to reduce memory usage. The viewer automatically manages memory with dynamic quality scaling.
Horizontal/paging not working
These are iOS-only features. Android uses vertical scroll.
See CONTRIBUTING.md for development setup.
MIT