The fastest way to use PouchDB on React Native 0.73+

The fastest way to use PouchDB on React Native 0.73+

Hi, it's Takuya here.

I've been updating the mobile version of Inkdrop, which is built with React Native.
It is a simple Markdown note-taking app, which supports syncing notes with the server and offer an offline-first viewing and editing experience.
To accomplish this, I've been using PouchDB, the JavaScript-based database that can sync with Apache CouchDB.

The PouchDB community was struggling to get it to work on React Native smoothly since it doesn't provide NodeJS-compatible APIs like encoding/decoding base64 or the crypto module out of the box.
In 2022, I shared how to use PouchDB on React Native in this blog post.
The technique was adequately performant by using a JSI-based SQLite driver, and polyfilling the missing modules with the native implementations respectively.

Since then, the circumstances around the React Native ecosystem has been sifnificantly changed.
This article is an updated version of how to use PouchDB on the latest React Native.

Mature JSI-based libraries and the NULL char problem solved

op-sqlite's performance comparison

There are now JSI-based libraries that are better designed and more performant, for example:

  • op-sqlite: Fastest SQLite library for react-native by @ospfranco
  • react-native-quick-crypto: ⚡️ A fast implementation of Node's crypto module written in C/C++ JSI
    • It internally uses my @craftzdog/react-native-buffer module, which improves encoding/decoding base64 performance.

React Native also introduced the new architecture: TurboModules and Fabric, which themselves leverage JSI to improve the communication performance between the native and JS layers.
It appears that a recent RN update, though I'm not use which commit, has solved the \u0000 string termination! It means that you no longer need a hack escaping \u0000 chars. Yay!

With these new libraries and fundamental improvements, you can use PouchDB much more smoothly and straightforwardly.

Introducing pouchdb-adapter-react-native-sqlite@4

Previsouly, I made pouchdb-adapter-react-native-sqlite to let PouchDB use SQLite on React Native.
I'm excited to announce v4, which now uses op-sqlite.
This time, I managed to make it directly call SQLite APIs and to get rid of the websql layer.

Thanks to the NULL termination issue gone, attachment support is back available now.

You can try an example project for a quick hands-on experience.

pouchdb-adapter-react-native-sqlite/example at master · craftzdog/pouchdb-adapter-react-native-sqlite
PouchDB adapter using ReactNative SQLite as its backing store - craftzdog/pouchdb-adapter-react-native-sqlite

How to use

Setting up the adapter is pretty easy in v4.

Install libraries

yarn add @op-engineering/op-sqlite react-native-quick-crypto @craftzdog/react-native-buffer
npx pod-install

Polyfill NodeJS APIs

Create a shim.ts file like so:

import { install } from 'react-native-quick-crypto';

install();

Configure babel to use the shim modules. First, you need to install babel-plugin-module-resolver.

yarn add --dev babel-plugin-module-resolver

Then, in your babel.config.js, add the plugin to swap the crypto, stream and buffer dependencies:

    plugins: [
      [
        'module-resolver',
        {
          extensions: ['.tsx', '.ts', '.js', '.json'],
          alias: {
            crypto: 'react-native-quick-crypto',
            stream: 'readable-stream',
            buffer: '@craftzdog/react-native-buffer',
          },
        },
      ],
    ],

Then restart your bundler using yarn start --reset-cache.

Install PouchDB and adapter

Now it's ready to use PouchDB!

yarn add pouchdb-core pouchdb-mapreduce pouchdb-replication pouchdb-adapter-http pouchdb-adapter-react-native-sqlite

Create pouchdb.ts:

import PouchDB from 'pouchdb-core'
import HttpPouch from 'pouchdb-adapter-http'
import replication from 'pouchdb-replication'
import mapreduce from 'pouchdb-mapreduce'
import sqliteAdapter from 'pouchdb-adapter-react-native-sqlite'

export default PouchDB.plugin(HttpPouch)
  .plugin(replication)
  .plugin(mapreduce)
  .plugin(sqliteAdapter)

How to use PouchDB

import PouchDB from './pouchdb'

const pouch = new PouchDB('mydb', {
  adapter: 'react-native-sqlite',
})

That's it!
Hope you find it useful and helpful.

Troubleshootings

Fails to install crypto shim with install() on launch

You amy get the following error when new arch is enabled:

 (NOBRIDGE) ERROR  Error: Failed to install react-native-quick-crypto: React Native is not running on-device. QuickCrypto can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.
 (NOBRIDGE) ERROR  TypeError: Cannot read property 'install' of undefined

For now, you have to edit:

  • lib/module/NativeQuickCrypto/NativeQuickCrypto.js

And comment them out:

  // Check if we are running on-device (JSI)
  // if (global.nativeCallSyncHook == null || QuickCryptoModule.install == null) {
  //   throw new Error('Failed to install react-native-quick-crypto: React Native is not running on-device. QuickCrypto can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.');
  // }