React Native offline-first build with PouchDB & CouchDB


Introduction

At YLD we have been doing some work with offline-first applications and, with the release of Service Workers, a major step has been taken in building these kind of applications in the browser. We have also been using CouchDB and PouchDB to access data while the Application is offline. At some point, we started to wonder if we could do the same on native mobile apps while using React Native.

The problem

Can we achieve an offline-first experience using React Native? Can we store data locally? Can we sync data between multiple devices?

The solution

For this purpose, we built a simple collaborative application where users can add or remove notes. You can find it [here](https://github.com/yldio/react-native-offline-first/” target=”_blank).

React Native

react-native-offline-first  
|-- index.ios.js  
|-- DocsApp.js  
|-- Docs.js  
|-- DocForm.js

The entry points for a react native application are index.ios.js and index.android.js, for iOS and Android devices respectively. However, for this particular example, we focused on iOS only.

  • DocsApp.js is the main component, also where the main state is kept.
  • Docs.js is the documents list.
  • DocForm.js is used to add new documents.

PouchDB

Luckily [PouchDB](https://pouchdb.com/” target=”_blank) exists and we can take advantage of its replication system to store data locally when offline and synchronise it with a remote [CouchDB](http://couchdb.apache.org/” target=”_blank) database once the application is back online.

Setup the databases

We can either create/use a local database, or use PouchDB as a client to a remote CouchDB instance.

const localDB = new PouchDB('docs');  
const remoteDB = new PouchDB('http://localhost:5984/docs');

Replicate the data from local store to remote CouchDB and vice-versa

The PouchDB API provides a method for bidirectional data replication. It accepts the live option, so that all changes continue to be replicated, and the retry option, to attempt replications if the application goes offline.

const sync = localDB.sync(remoteDB, {  
  live: true,  
  retry: true  
});

Persisting the data

The data is persisted in the local PouchDB and replicated when possible.

onDocSubmit(doc) {  
  localDB.put({\_id: doc, content: doc, imageUrl: imageUrl})  
    .catch(console.log.bind(console, 'Error inserting'));  
}

Subscribe to database changes

We can also subscribe to the changes feed so that after receiving a change — either from the remote server or the local user — the UI is updated, either by creating, updating or deleting a document.

class DocsApp extends Component {  
  /\* ... \*/  

  componentDidMount {  
    localDB.changes({  
      live: true,  
      include\_docs: true //Include all fields in the doc field  
    }).on('change', this.handleChange.bind(this))  
  }  

  handleChange(change) {  
    var doc = change.doc;  

    if (!doc) {  
      return;  
    }  

    if (doc.\_deleted) {  
      this.removeDoc(doc);  
    } else {  
      this.addDoc(doc);  
    }  
  }  

  addDoc(newDoc) {  
    if (!\_.find(this.state.docs, '\_id', newDoc.\_id)) {  
      this.setState({  
        docs: this.state.docs.concat(newDoc)  
      });  
    }  
  }  

  removeDoc(oldDoc) {  
    this.setState({  
      docs: this.state.docs.filter(doc => doc.\_id !== oldDoc.\_id)  
    });  
  }  

  /\* ... \*/  

}

If you want to explore more about managing UI state, check out [this article from the PouchDB website](https://pouchdb.com/2015/02/28/efficiently-managing-ui-state-in-pouchdb.html” target=”_blank).

Ok, now we are syncing the data, but what about other assets, such as images?

React Native provides a native component to display images, with some functionality that can help us.

<Image  
  source={{uri: doc.imageUrl}}  
  defaultSource={require('./assets/images/image-loading.png')}  
/>

The source attribute is self-explanatory and the defaultSource is used to display a static image when loading the source.

This component will cache the image after the first download, so further requests of the same image won’t trigger a network request.

In the PoC application we built, we are only generating 3 different images. After that, we are using the same URLs so that we can see the application using the cached images.

var imageUrl = 'http://unsplash.it/200?random&t=' + this.imgId;  
localDB.put({\_id: doc, content: doc, imageUrl: imageUrl})  
  .catch(console.log.bind(console, 'Error inserting'));

Conclusion

Building a React Native offline-first data-driven application using PouchDB and CouchDB is, in many ways, similar to the way you would build a web app using React. By using PouchDB you can access a local store that, once there is network connectivity, syncs data to and from a remote CouchDB service.

Written by João Almeida — Software Engineer at YLD.

You may also like:


Written by YLDJuly 5th, 2016


Share this article

Find us

London - HQ

9 Dallington Street

London

EC1V 0LN

+44(0) 203 514 4678

hello@yld.io

Lisbon

Rua Ramalho Ortigão 8

3º Esquerdo

1070-230

Lisboa

Porto

Rua Sá da Bandeira 819

2º Esquerdo

4000-438

Porto