@michelgutner/react-native-pdf-viewer
v0.1.0
Published
High-performance PDF viewer for React Native — New Architecture (Fabric). Uses PDFKit on iOS and PdfRenderer on Android. No native dependencies, no WebViews.
Maintainers
Readme
RNPdfViewer
A high-performance PDF viewer for React Native built on the New Architecture (Fabric). Uses PDFKit on iOS and android.graphics.pdf.PdfRenderer on Android — no native dependencies, no WebViews.
Requirements
| | Version | |---|---| | React Native | ≥ 0.75 (New Architecture required) | | iOS | ≥ 15.1 | | Android | API ≥ 24 |
New Architecture only. The component uses Fabric and Codegen. Legacy Architecture is not supported.
Installation
npm install @michelgutner/react-native-pdf-vieweriOS
cd ios && pod installAndroid
No extra steps. The library uses the built-in PdfRenderer API.
Usage
import RNPdfViewer from '@michelgutner/react-native-pdf-viewer';
<RNPdfViewer
style={{ flex: 1 }}
source={{ url: 'https://example.com/document.pdf' }}
onLoadEnd={e => console.log('loaded', e.nativeEvent.url)}
onPageChange={e => console.log(`page ${e.nativeEvent.page} of ${e.nativeEvent.totalPages}`)}
/>Props
Source
| Prop | Type | Description |
|---|---|---|
| source | { url: string } | HTTP/HTTPS or file:// URL of the PDF |
Display
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | 'VERTICAL' \| 'HORIZONTAL' | 'VERTICAL' | Scroll direction. Horizontal uses page-snap. |
| autoScale | boolean | true | Scale pages to fit the view width |
| colorMode | 'default' \| 'night' \| 'sepia' | 'default' | Color theme |
| twoPageMode | boolean | false | Show two pages side by side (landscape / tablet) |
Zoom
| Prop | Type | Default | Description |
|---|---|---|---|
| minZoom | number | 0.25 | Minimum scale factor |
| maxZoom | number | 5.0 | Maximum scale factor |
Navigation
| Prop | Type | Description |
|---|---|---|
| page | number | Jump to this 1-based page number when the value changes |
Search
| Prop | Type | Description |
|---|---|---|
| searchTerm | string | Highlights all matches in the document |
| nextSearchFieldIndex | number | Increment to navigate to the next match |
| previousSearchFieldIndex | number | Increment to navigate to the previous match |
Password-protected PDFs
| Prop | Type | Description |
|---|---|---|
| password | string | Password to unlock a protected PDF |
Loading indicator
| Prop | Type | Default | Description |
|---|---|---|---|
| loadingEnabled | boolean | true | Show a spinner while the PDF loads |
| loadingSize | 'small' \| 'medium' \| 'large' | 'large' | Spinner size |
Thumbnails
| Prop | Type | Description |
|---|---|---|
| thumbnailWidth | number | Width in pixels for generated thumbnails. 0 disables generation. Fires onThumbnailReady once per page. |
Full-screen page overlay
| Prop | Type | Description |
|---|---|---|
| fullScreenPage | number | Opens a 1-based page in a full-screen overlay. Set to 0 to close. |
Performance
| Prop | Type | Default | Description |
|---|---|---|---|
| pageCacheSize | number | 5 | Number of pre-rendered pages to keep in memory (Android) |
Imperative actions
These props work as triggers — increment the value to fire the action once.
| Prop | Type | Description |
|---|---|---|
| printTrigger | number | Open the system print dialog |
| shareTrigger | number | Open the system share sheet |
const [printTrigger, setPrintTrigger] = useState(0);
// fire:
setPrintTrigger(v => v + 1);
<RNPdfViewer printTrigger={printTrigger} ... />Events
| Event | Payload | Description |
|---|---|---|
| onLoadStart | { url: string } | PDF download started |
| onLoadEnd | { url: string } | PDF is ready and first page is rendered |
| onPageChange | { page: number; totalPages: number } | User scrolled to a different page |
| onSearchTermData | { totalCount: number; currentIndex: number } | Search completed — total matches and active index |
| onError | { message: string } | Download or parse error, or wrong password |
| onOutlineReady | { outlineJson: string } | Table of contents available. Parse with JSON.parse(outlineJson) → Array<{ title, page, level }> |
| onLinkPress | { url: string } | User tapped a link inside the PDF |
| onThumbnailReady | { page: number; base64: string } | A page thumbnail is ready (JPEG, base64-encoded) |
| onFullScreenClose | { page: number } | User closed the full-screen page overlay |
Examples
Search with navigation
const [term, setTerm] = useState('');
const [next, setNext] = useState(0);
const [prev, setPrev] = useState(0);
const [matches, setMatches] = useState({ totalCount: 0, currentIndex: 0 });
<RNPdfViewer
source={{ url: PDF_URL }}
searchTerm={term}
nextSearchFieldIndex={next}
previousSearchFieldIndex={prev}
onSearchTermData={e => setMatches(e.nativeEvent)}
/>
// Navigate:
<Button title="Next" onPress={() => setNext(v => v + 1)} />
<Button title="Previous" onPress={() => setPrev(v => v + 1)} />Table of contents
const [outline, setOutline] = useState([]);
<RNPdfViewer
source={{ url: PDF_URL }}
onOutlineReady={e => setOutline(JSON.parse(e.nativeEvent.outlineJson))}
/>
{outline.map(item => (
<Text key={item.page} style={{ paddingLeft: item.level * 16 }}>
{item.title}
</Text>
))}Thumbnails strip
const [thumbs, setThumbs] = useState({});
<RNPdfViewer
source={{ url: PDF_URL }}
thumbnailWidth={80}
onThumbnailReady={e => {
const { page, base64 } = e.nativeEvent;
setThumbs(prev => ({ ...prev, [page]: base64 }));
}}
/>
{Object.entries(thumbs).map(([page, b64]) => (
<Image
key={page}
source={{ uri: `data:image/jpeg;base64,${b64}` }}
style={{ width: 80, height: 110 }}
/>
))}Platform notes
| Feature | iOS | Android |
|---|---|---|
| Render engine | PDFKit | android.graphics.pdf.PdfRenderer |
| Search / highlight | ✅ | ✅ API 35+ (silent on older) |
| Table of contents | ✅ | ✅ API 35+ (empty on older) |
| Clickable links | ✅ | ✅ API 35+ |
| Print | ✅ | ✅ |
| Share | ✅ | ✅ |
| Night / Sepia mode | ✅ | ✅ |
| Two-page mode | ✅ | ✅ |
| Password-protected PDFs | ✅ | ✅ |
License
MIT
