A performant way to use PouchDB@7 on React Native in 2022
Tags: pouchdb, reactnative, couchdb, javascript
Published: 2022-05-01 01:13:49

Hi, it's [Takuya](https://twitter.com/inkdrop_app) here.
I've been developing a React Native app which syncs user data with CouchDB. The app is called [Inkdrop](https://www.inkdrop.app/).

I've published several posts on how to use PouchDB on React Native in the past:

- [Hacking PouchDB to Use It on React Native](https://dev.to/craftzdog/hacking-pouchdb-to-use-on-react-native-1gjh)
- [Created PouchDB@7 for React Native](https://dev.to/craftzdog/created-pouchdb-7-for-react-native-23g6)

This article is an update for using PouchDB on RN.

Fortunately, [PouchDB](https://github.com/pouchdb/pouchdb) is still maintained, and recently they've shipped v7.3.0 🎉. Much appreciated the community effort.

## TL;DR

- 2~5x speed improvement!
- Less library tweaks & simpler setup
- Attachments are no longer supported
- How to use

## Use a JSI-based SQLite driver

Oscar Franco created a great SQLite driver [react-native-quick-sqlite](https://github.com/ospfranco/react-native-quick-sqlite), which provides a low-level API to execute SQL queries, fast bindings via [JSI](https://formidable.com/blog/2019/jsi-jsc-part-2/). JSI has a smaller overhead than the traditional RN bridge's.
So, this is much faster than [react-native-sqlite-2](https://github.com/craftzdog/react-native-sqlite-2), which I made to get PouchDB to work on RN before.

I wanted to use it in my project, so I made [react-native-quick-websql](https://github.com/craftzdog/react-native-quick-websql/), which is a thin wrapper to make quick-sqlite compatible with WebSQL. It works flawlessly!

I'll discuss how it improves the performance later.

## Tackling the NULL character issue (again)

There is still an issue in React Native - It can't store string data with `\u0000`.

- [JavaScript strings with NULL character are not handled properly when passed to Native Modules · Issue #12731 · facebook/react-native](https://github.com/facebook/react-native/issues/12731)

To avoid the issue, the NULL characters are escaped/unescaped in [react-native-sqlite-2](https://github.com/craftzdog/react-native-sqlite-2/blob/master/src/SQLiteDatabase.ts#L50) like so:

```js
function escapeBlob(data: any) {
  if (typeof data === 'string') {
    return data
      .replace(/\u0002/g, '\u0002\u0002')
      .replace(/\u0001/g, '\u0001\u0002')
      .replace(/\u0000/g, '\u0001\u0001')
  } else {
    return data
  }
}
```

But it's not ideal because it would affect the performance.

PouchDB internally uses `\u0000` for indexing documents with MapReduce in [pouchdb-collate](https://github.com/pouchdb/pouchdb/blob/fafcd701a26171abc42b9c64ff4abc3c418c0d81/packages/node_modules/pouchdb-collate/src/index.js#L116-L119):

```js
/ convert the given key to a string that would be appropriate
// for lexical sorting, e.g. within a database, where the
// sorting is the same given by the collate() function.
function toIndexableString(key) {
  var zero = '\u0000';
  key = normalizeKey(key);
  return collationIndex(key) + SEP + indexify(key) + zero;
}
```

I didn't see why it should be `\u0000`, so I've changed the zero marker to `\u0003`:

- https://github.com/craftzdog/pouchdb-react-native/commit/228f68220fe31236f6630b71c030eef29ae6e7a8

And it turns out that it works!
Now the overhead of escaping NULL characters has been removed.

## It's 2~5x faster 💨

I've tested it on my Google Pixel 5 and it turns out it's blazing fast. Here is a quick benchmark:

|            | sqlite-2 | quick-sqlite + quick-websql |
| --------- | --------: | --------: |
| Non-cached query for 38 docs | 4,510 ms | 984 ms | 
| Cached query for 38 docs | 275 ms |  75 ms |
| Cached query for 1422 docs | 636 ms | 206 ms |

PouchDB performs MapReduce to index the documents based on [design documents](https://pouchdb.com/guides/queries.html).
That's why the first query takes a long time.
But thanks to the small overheads, it's now about 4~5x faster.

I can't wait to release the new version of my app.

## Attachments are no longer supported

In the past work, the main focus was to get PouchDB able to store attachments. But [I no longer store them in PouchDB](https://blog.inkdrop.app/how-i-improved-my-react-native-app-50x-faster-13d566061e84), so that requirement has been dropped.
But that allows you to simply use the official PouchDB packages except for `pouchdb-collate` as I mentioned above.

## How to use

### Install

Install dev package:

```
yarn add -D babel-plugin-module-resolver
```

Install polyfill packages:

```sh
yarn add events react-native-get-random-values react-native-quick-base64
```

Install pouchdb packages:

```sh
yarn add pouchdb-core pouchdb-replication pouchdb-mapreduce pouchdb-adapter-http
```

Install the patched package:

```sh
yarn add @craftzdog/pouchdb-collate-react-native
```   

Install storage adapter packages:

```sh
yarn add pouchdb-adapter-react-native-sqlite react-native-quick-sqlite react-native-quick-websql
```

 - [react-native-quick-sqlite](https://github.com/ospfranco/react-native-quick-sqlite) - A fast bindings via JSI for SQLite
 - [react-native-quick-websql](https://github.com/craftzdog/react-native-quick-websql/) - WebSQL wrapper for quick-sqlite
 - [pouchdb-adapter-react-native-sqlite](https://github.com/craftzdog/pouchdb-adapter-react-native-sqlite) - PouchDB adapter for SQLite with those two modules

6. Install CocoaPods:

```sh
npx pod-install
```

### Configure

Make a `shim.js`:

```js
import {shim} from 'react-native-quick-base64'

shim()

// Avoid using node dependent modules
process.browser = true
```

then, require it at the beginning of your `index.js`.

Edit your `babel.config.js` like so:

```js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        alias: {
          'pouchdb-collate': '@craftzdog/pouchdb-collate-react-native',
        },
      },
    ],
  ],
}
```

### Initialize

Create `pouchdb.ts` like so:

```ts
import 'react-native-get-random-values'
import PouchDB from 'pouchdb-core'
import HttpPouch from 'pouchdb-adapter-http'
import replication from 'pouchdb-replication'
import mapreduce from 'pouchdb-mapreduce'
import SQLiteAdapterFactory from 'pouchdb-adapter-react-native-sqlite'
import WebSQLite from 'react-native-quick-websql'

const SQLiteAdapter = SQLiteAdapterFactory(WebSQLite)

export default PouchDB.plugin(HttpPouch)
  .plugin(replication)
  .plugin(mapreduce)
  .plugin(SQLiteAdapter)
```

Then, import and use it as usual:

```ts
import PouchDB from './pouchdb'

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

That's it!

### Limitations & Debugging

As the libraries use JSI for synchronous native methods access, remote debugging (e.g. with Chrome) is no longer possible.
Instead, you should use [Flipper](https://github.com/facebook/flipper).


Hope you find it useful and helpful.

* * *

- Follow me on [Twitter](https://twitter.com/inkdrop_app) & [Instagram](https://instagram.com/craftzdog)
- [Inkdrop — Markdown note-taking app](https://www.inkdrop.app/)

[![Subscribe Newsletter](https://miro.medium.com/proxy/1*-J5tp1_lulY9QknoLEv6zA.png)](http://eepurl.com/dNgJo6)

[![My YouTube channel](https://miro.medium.com/proxy/1*r-daItizHpAU-fQZk3ISag.png)](https://www.youtube.com/c/devaslife)