The PDF Problem Nobody Talks About in React Native
Every React Native app eventually meets a PDF. A contract that needs signing. An invoice to display. A report that lives in an S3 bucket. And every time, the developer opens npm and types “react-native pdf” — and finds the same libraries that have been there for years, built for an architecture React Native is actively moving away from.
I got tired of working around that. So I built react-native-pdfrender — a PDF library written from scratch for the New Architecture. No legacy bridge. No JavaScript fallbacks. No dependency chain reaching back to 2018.
Here’s an honest look at why the existing options fall short, and what a ground-up solution looks like.
What you’re actually working with today
react-native-pdf is the community default. 105 published versions. It works. But crack open its package.json and you'll find react-native-blob-util as a peer dependency and crypto-js in the runtime bundle. To display a PDF, you install two additional packages, manually patch android/app/build.gradle to resolve .so file conflicts, and run pod install. The library handles remote URLs, caching, password-protected files, horizontal scrolling — it has accumulated features across years of maintenance. That's the trade-off: breadth for weight.
rn-pdf-reader-js takes the opposite approach and tells you exactly what it is in the tagline: "Read a PDF just with JS (no native libs needed)." That's not a feature — it's a workaround. Under the hood it renders PDF pages by driving a WebView. It requires react-native-webview, expo-file-system, and expo-constants as manual installs. The rendering happens in a browser engine, not a native PDF renderer.
react-native-pdf-light is the most honest competitor. It uses android.graphics.pdf.PdfRenderer and CGPDFDocument — the right native APIs — and version 3.x added New Architecture support. It's genuinely good. Its explicit design goal is a restricted feature set: a FlatList-based full-document viewer and a single-page PdfView building block. Zoom requires a separate install of react-native-gesture-handler. It gives you primitives and asks you to build on top of them.
These are all reasonable libraries. They make sense for the constraints they were built inside. But none of them were designed with the New Architecture as the foundation.
What “New Architecture first” actually means
The New Architecture isn’t a performance checkbox. It’s a different execution model.
Codegen generates the native bindings from your TypeScript specs at build time. The property types your component declares in TypeScript and the types the native layer expects are the same types, enforced by the compiler. react-native-pdfrender uses this for every prop — uri, initialPage, defaultZoom, isFullScreen. A type mismatch surfaces at build, not as a runtime crash on a user's device.
Fabric components render synchronously on the UI thread. When a user pinches to zoom or the app transitions to fullscreen, there is no JavaScript → serialization → bridge → native round trip. The interaction paths that matter most — viewport changes, scroll position, zoom state — are all in the native renderer’s hot path.
TurboModules load on first use. If a user never opens the PDF viewer in your app, the module is never initialized. The old bridge initialized everything at startup.
Jetpack Compose on Android means the viewer UI is built with the same toolkit Google is pushing all Android development toward. No XML layout files to worry about, no legacy View system threading model.
Zero dependencies, taken seriously
Open react-native-pdfrender's package.json. The dependencies field is empty.
This sounds like a small detail. It isn’t. Every runtime dependency is a transitive surface area — security vulnerabilities, version conflicts, breaking changes outside your control, bundle size. react-native-pdf ships crypto-js as a runtime dependency for PDF password handling. rn-pdf-reader-js needs three separate Expo packages installed manually. These aren't criticisms — they're trade-offs. The features they add require dependencies.
react-native-pdfrender made a different trade-off: no features that require runtime JavaScript packages. PDF rendering, page navigation, zoom, fullscreen — everything is native code calling platform APIs that have shipped on every supported iOS and Android version for years.
A real integration: file picker to rendered PDF in 30 lines
The example app in the repo is the clearest demonstration of what the API feels like in practice. It’s a single screen. A user taps Choose PDF, the native document picker opens, they select a file, and the PDF renders inline. No intermediate steps.
import { PdfViewerView } from 'react-native-pdfrender';
import DocumentPicker, { types } from 'react-native-document-picker';export default function App() {
const [pdfUri, setPdfUri] = React.useState<string | null>(null);
const [isFullScreen, setIsFullScreen] = React.useState(false); const pickPdf = async () => {
const result = await DocumentPicker.pickSingle({ type: types.pdf });
setPdfUri(result.uri);
}; return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={pickPdf}>
<Text>{pdfUri ? 'Change PDF' : 'Choose PDF'}</Text>
</TouchableOpacity> {pdfUri ? (
<PdfViewerView
uri={pdfUri}
initialPage={0}
defaultZoom={1}
isFullScreen={isFullScreen}
onFullScreenChange={setIsFullScreen}
style={{ flex: 1 }}
/>
) : (
<EmptyState />
)}
</View>
);
}
The URI from the document picker — content:// on Android, file:// on iOS — goes straight into the component. No copying to a temp path. No base64 encoding. Both URI schemes are handled natively.
Get Shubham Keshari’s stories in your inbox
Join Medium for free to get updates from this writer.
Fullscreen state is controlled from JavaScript: the viewer exposes onFullScreenChange so the JS side stays in sync with the toolbar toggle. You own the state; the viewer doesn't manage it for you.
The hook path: when you don’t want an inline viewer
For cases where the PDF should appear modally — on top of existing content — usePdfViewer gives you an imperative API:
const { open, dismiss, getPageCount } = usePdfViewer({
onFullScreenChanged: (isFS) => setFullScreen(isFS),
onZoomChanged: (zoom) => setZoom(zoom),
onDismiss: () => console.log('closed'),
});// somewhere in your render tree:
<Button title="View Contract" onPress={() => open('file:///path/to/contract.pdf')} />Same native renderer, different presentation model. The viewer opens as a native overlay, exposes lifecycle callbacks, and dismisses cleanly.
What it doesn’t do (yet)
Honesty matters in open source. react-native-pdfrender doesn't support:
- Remote URL loading (you need to download the file first — the README shows the pattern with
react-native-fs) - Password-protected PDFs
- Text selection or annotation
- Horizontal scroll layout
- Thumbnail strip / page preview
Some of these are planned. Others are deliberate scope decisions — this is a renderer, not a document editor. If your app needs password-protected PDF support today, react-native-pdf is the right choice. If you're building on the New Architecture and want clean, native rendering with a minimal footprint, this is built for that.
Getting started
npm install react-native-pdfrender
# or
yarn add react-native-pdfrenderiOS — cd ios && pod install
Android — auto-linking. Nothing else.
Requirements: React Native 0.70+, iOS 13+, Android SDK 24+, Node 18+
Questions, issues, PRs — all welcome on GitHub.




