Building Offline First App in React Native using PouchDB and CouchDB

Offline First App in React Native using PouchDB and CouchDB

Here is an example of Building Offline First App in React Native using PouchDB and CouchDB. In this example we will see what is offline first approach? Why PouchDB and CouchDB? How to develop any app in React Native using offline first approach?

What is “Offline First” Approach?

The “Offline First” concept is very simple and the goal is that your app will work beautifully all the time, let it be online or offline. In this approach data will be downloaded to the user’s device and then application will access the data and once it modifies the data it will be stores back the device database by the app and then taken to the server. So in simple words instead of connecting your application directly to the server app will store the data in offline database and the sync the data to the server.

offline_first_approach

Issue with “Offline First” Approach?

Theoretically offline first concept is very good but building the same application is a bit complex process.

  1. How to sync data to the local db from server?
  2. What about syncing the local data back to the server, who will do that perfectly?
  3. What if anyone else updated the same record which you have updated in the offline mode, Who will resolve the conflict?
  4. How to reflect the realtime changes (Ex. like/comment counts on your fb post)

There are so many other points as well which can take you in different state to ignore this approach without a proper solution.

How to achieve “Offline First” Approach?

If we talk about the temporary solution then we can create the same application using any local database and server API. You just have to manage the sync flag with the record which will help you to identify if this data is being synced or not and whenever you create/update/delete any record you have to mark it this sync flag as false so that it can be synced next time. As I mentioned this is just a temporary solution as you have to manage the sync triggers and network status also.

I was personally creating this kind of apps with the temporary solution but after a certain time when you will have lots of transactions then you will feel performance issue and will feel the lose of data in case of conflicts. To solve the issues of temporary solution I was looking for the perfect solution and then I found PouchDB-CouchDB to solve the problem.

PouchDB  is to help developers build applications that work same in offline mode as they work in online mode. Pouch db tagline says The Database that Syncs!. It enables applications to store data locally while offline, then synchronize it with CouchDB and compatible servers when the application is back online, keeping the user’s data in sync no matter where they next login.

What is PouchDB?

PouchDB is an open source in-browser database API written in JavaScript. It is a JavaScript implementation of CouchDB. Its goal is to emulate the CouchDB API with near-perfect fidelity. Using this, we can build applications that work offline and online. PouchDB uses WebSQL and IndexedDB internally to store the data. PouchDB then syncs data automatically to Apache CouchDB, which is again a NoSQL database.

What is CouchDB?

CouchDB is a NoSQL database created in 2005 by Damien Katz, and now maintained by the Apache Software Foundation. If you are a JavaScript developer, you probably use CouchDB every day, because it’s the core technology that powers npm.

CouchDB lets your data flow seamlessly between server clusters to mobile phones and web browsers, enabling a compelling offline-first user-experience while maintaining high performance and strong reliability.

Today there are two major database companies that can trace their lineage back to CouchDB: Couchbase and Cloudant. Both of them are separate products compared to CouchDB. However, these databases share the same CouchDB sync protocol. This means that PouchDB can sync with either one of them, and you can always swap out one database for another.

Why CouchDB?

With so many SQL and NoSQL databases out there – MongoDB, PostgreSQL, MySQL, etc. – you may wonder why we chose to implement CouchDB instead of the others.

We have two very good answers to that question: HTTP and sync.

  1. HTTP: When working with databases, we’re often accustomed to writing some kind of conversion layer between the database and our client-side applications. This means, however, that we are just translating database queries into RESTful HTTP calls, over and over. For every app we write.
    CouchDB throws this out the window by daring us to talk to the database directly, from our client-side apps. And it does so by using HTTP as its primary means of communication. No special protocol, no special drivers: just REST and HTTP. You can communicate with CouchDB entirely through your browser, curl, or a REST client like Postman.
  2. Sync: Another unique feature of CouchDB is that it was designed from the bottom-up to enable easy synchronization between different databases.
    For example, if you are worried about latency in your client-side applications, you can simply set up one CouchDB in Europe, another in North America, and another in Asia. After enabling continuous two-way replication between these databases, your clients can simply talk to whichever one is closer.

Installation of CouchDB

We will see the PouchDB setup and app development below but before that let’s setup our CouchDB.

First download the CouchDB from its Official Website. Install it and hit the URL in your browser.

http://127.0.0.1:5984/_utils/

This will open up an interface called Futon. This is a built in interface of CouchDB. If you have been asked to setup the password then processing accordingly. Please remember the username and password you setup as we will need that for the authentication when connecting PouchDB with CouchDB.

couchdb_dashboard
Now create a database by clicking on the Create Database button from right top and, giving the name of the database.
Data stored in CouchDB are called documents and uses _id as a unique identifier of each document. You can either give your own _id while creating the record or leave it on couchDB to generate unique hash for every document.

If everything was right then you will have your CouchDB database ready with you. Now let’s move to create an app using PouchDB which will sync the data to this CouchDB.

PouchDB and CouchDB Example Description

In this example, we will build a complete offline first application using PouchDb which will be in sync with the CouchDB database running on localhost(port:5984). In the application we will have a HomeScreen with the following options

  • Register User: To Register the User (Create/Insert Record)
  • View All User: To View All Users (Get All Records)
  • View User: To View Singel Users By Id (Get Single Record, Filtered)
  • Update User: To Update the User (Update Record)
  • Delete User: To Delete the User (Delete Record)
  • RealTime Record Add/Remove: Add/Remove functionality in single screen to see real time updated

We will be having some custom components like Mybutton, Mytext, Mytextinput which will be used in place of react-native Button, Text, and TextInput. We will have a config.js file also which is having the CouchDB connectivity related configurations which you can change as per your need.

To Make a React Native App

Getting started with React Native will help you to know more about the way you can make a React Native project. We are going to use react native command line interface to make our React Native App.

If you have previously installed a global react-native-cli package, please remove it as it may cause unexpected issues:

npm uninstall -g react-native-cli @react-native-community/cli

Run the following commands to create a new React Native project

npx react-native init ProjectName

If you want to start a new project with a specific React Native version, you can use the --version argument:

npx react-native init ProjectName --version X.XX.X

Note If the above command is failing, you may have old version of react-native or react-native-cli installed globally on your pc. Try uninstalling the cli and run the cli using npx.

This will make a project structure with an index file named App.js in your project directory.

Installation of Dependencies

To install the dependencies open the terminal and jump into your project

cd ProjectName

1. Install pouchdb-react-native dependency to use PouchDB

npm install pouchdb-react-native --save

2. Install pouchdb-authentication dependency to use PouchAuth for the authentication

npm install pouchdb-authentication --save

3. Install following dependencies for react-navigation

npm install @react-navigation/native --save
npm install @react-navigation/stack --save
npm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view --save

4. For the array operation install lodash

npm install lodash --save

These commands will copy all the dependencies into your node_module directory.

CocoaPods Installation

Please use the following command to install CocoaPods

cd ios && pod install && cd ..

Project Structure

Please create the following project structure and copy the code given below

react_native_pouchdb_couchdb_file_structure

Code to Build React Native App with PouchDB and CouchDB

Open the project directory and replace the following code

App.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/

import 'react-native-gesture-handler';

import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';

import HomeScreen from './pages/HomeScreen';
import RegisterUser from './pages/RegisterUser';
import UpdateUser from './pages/UpdateUser';
import ViewUser from './pages/ViewUser';
import ViewAllUser from './pages/ViewAllUser';
import DeleteUser from './pages/DeleteUser';
import RealTimeAddUpdateUser from './pages/RealTimeAddUpdateUser';

const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="HomeScreen">
        <Stack.Screen
          name="HomeScreen"
          component={HomeScreen}
          options={{
            title: 'Home', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
        <Stack.Screen
          name="View"
          component={ViewUser}
          options={{
            title: 'View User', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
        <Stack.Screen
          name="ViewAll"
          component={ViewAllUser}
          options={{
            title: 'View Users', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
        <Stack.Screen
          name="Update"
          component={UpdateUser}
          options={{
            title: 'Update User', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
        <Stack.Screen
          name="Register"
          component={RegisterUser}
          options={{
            title: 'Register User', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
        <Stack.Screen
          name="Delete"
          component={DeleteUser}
          options={{
            title: 'Delete User', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
        <Stack.Screen
          name="RealTimeAddUpdate"
          component={RealTimeAddUpdateUser}
          options={{
            title: 'RealTime Add/Update User', //Set Header Title
            headerStyle: {
              backgroundColor: '#009999', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default App;

pages/components/Mybutton.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Custom Button

import React from 'react';
import {TouchableOpacity, Text, StyleSheet} from 'react-native';

const Mybutton = (props) => {
  return (
    <TouchableOpacity
      style={styles.button}
      onPress={props.customClick}>
      <Text style={styles.text}>{props.title}</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    alignItems: 'center',
    backgroundColor: '#009999',
    color: '#ffffff',
    padding: 10,
    marginTop: 16,
  },
  text: {
    color: 'white',
    fontWeight: 'bold',
  },
});

export default Mybutton;

pages/components/Mytext.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Custom Text

import React from 'react';
import {Text, StyleSheet} from 'react-native';

const Mytext = (props) => {
  return <Text style={styles.text}>{props.text}</Text>;
};

const styles = StyleSheet.create({
  text: {
    color: '#111825',
    fontSize: 18,
    marginTop: 16,
    marginLeft: 35,
    marginRight: 35,
    textAlign: 'center',
  },
});

export default Mytext;

pages/components/Mytextinput.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Custom TextInput

import React from 'react';
import {View, TextInput} from 'react-native';

const Mytextinput = (props) => {
  return (
    <View
      style={{
        marginTop: 10,
        borderColor: '#007FFF',
        borderWidth: 1,
      }}>
      <TextInput
        underlineColorAndroid="transparent"
        placeholder={props.placeholder}
        placeholderTextColor="#007FFF"
        keyboardType={props.keyboardType}
        onChangeText={props.onChangeText}
        returnKeyType={props.returnKeyType}
        numberOfLines={props.numberOfLines}
        multiline={props.multiline}
        onSubmitEditing={props.onSubmitEditing}
        style={props.style}
        blurOnSubmit={false}
        value={props.value}
      />
    </View>
  );
};

export default Mytextinput;

pages/config.js

  1. In case of any change in CouchDB URL please update the remoteDB URL (http://localhost:5984/<db> in below code)
  2. Update the username(admin) and password(admin) which you have created while setting up the CouchDB
  3. Update the database name which comes in the CouchDB URL, in the below code docs was my database to store the records, you can replace it with your own
// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/

import PouchDB from 'pouchdb-react-native';
import PouchAuth from 'pouchdb-authentication';

const localDB = new PouchDB('docs');
// Please update the CouchDB URL in case of any change
// Here "/docs" is the my database name
// You can change it with your own database name
const remoteDB = new PouchDB(
  'http://localhost:5984/docs',
  {skip_setup: true}
);
PouchDB.plugin(PouchAuth);

const syncStates = [
  'change',
  'paused',
  'active',
  'denied',
  'complete',
  'error',
];
// Please update the username and password of the couchDB
remoteDB.login('admin', 'admin').then(function () {
  const sync = localDB.sync(remoteDB, {
    live: true,
    retry: true,
  });
  syncStates.forEach((state) => {
    sync.on(state, setCurrentState.bind(this, state));
    function setCurrentState(state) {
      console.log('[Sync:' + state + ']');
    }
  });
});

export default localDB;

pages/RealTimeAddUpdateUser.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/

import React, {useState, useEffect} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  View,
  Text,
  ScrollView,
  TextInput,
} from 'react-native';

import _ from 'lodash';
import Mybutton from './components/Mybutton';
import db from './config';

const RealTimeAddUpdateUser = () => {
  let [docs, setDocs] = useState([]);
  let [inputDoc, setInputDoc] = useState('');

  useEffect(() => {
    db.allDocs({include_docs: true})
      .then((results) => {
        let temp = results.rows.map((row) => row.doc);
        setDocs(temp);
        addLiveUpdateListner();
      })
      .catch((err) => {
        alert('Error in fetching data: ', err);
        addLiveUpdateListner();
      });
  }, []);

  const addLiveUpdateListner = () => {
    db.changes({
      live: true,
      include_docs: true,
      ascending: true,
    })
      .on('change', (change) => {
        // console.log('[Change:Change]', change);
        let doc = change.doc;
        if (!doc) return;
        if (doc._deleted) {
          console.log('delete doc => ', doc);
          removeDoc(doc);
        } else {
          console.log('add doc => ', doc);
          addDoc(doc);
        }
      })
      .on(
        'complete',
        console.log.bind(console, '[Change:Complete]')
      )
      .on(
        'error',
        console.log.bind(console, '[Change:Error]')
      );
  };

  const addDoc = (newDoc) => {
    setDocs((docs) => {
      if (!_.find(docs, {_id: newDoc._id})) {
        return docs.concat(newDoc);
      } else {
        return docs.map((item) => (
          item._id === newDoc._id ? newDoc : item
        ));
      }
    });
  };

  const removeDoc = (oldDoc) => {
    setDocs((docs) => docs.filter(
      (doc) => doc._id !== oldDoc._id)
    );
  };

  const onDocSubmit = () => {
    db.post({name: inputDoc, contact: '', address: ''})
      .then((doc) => {
        console.log('doc', doc);
        if (!doc.ok) {
          alert('Insertion Failed');
          return;
        }
        setInputDoc('');
      })
      .catch((error) => alert('Error Inserting -> ' + error));
  };

  const onDocRemove = (oldDoc) => {
    db.remove(oldDoc)
      .then((doc) => {
        console.log('doc', doc);
        if (!doc.ok) {
          alert('Removal Failed');
          return;
        }
      })
      .catch((error) => alert('Error -> ' + error));
  };

  const renderDoc = (doc, index) => {
    return (
      <View
        style={{
          padding: 16,
          marginVertical: 10,
          backgroundColor: 'white',
          borderColor: '#E8E8E8',
          borderWidth: 1,
          borderBottomColor: '#D4D4D4',
          borderBottomWidth: 1,
          borderRadius: 2,
        }}
        key={index}>
        <Text>Id: {doc._id}</Text>
        <Text>Name: {doc.name}</Text>
        <Text>Contact: {doc.contact}</Text>
        <Text>Address: {doc.address}</Text>
        <Mybutton
          title="Remove"
          customClick={() => onDocRemove(doc)}
        />
      </View>
    );
  };

  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={{flex: 1, padding: 16}}>
        <View style={{flexDirection: 'row'}}>
          <TextInput
            style={{
              flex: 1,
              borderColor: 'black',
              height: 40,
              borderWidth: 0.5,
              marginTop: 14,
              backgroundColor: 'white',
              padding: 10,
            }}
            placeholder="Enter Name"
            onChangeText={(inputDoc) => setInputDoc(inputDoc)}
            value={inputDoc}
          />
          <Mybutton
            title="Submit"
            customClick={onDocSubmit}
          />
        </View>
        <ScrollView>
          {docs.map((doc, index) => renderDoc(doc, index))}
        </ScrollView>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  //containerForm
  containerForm: {
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 10,
    marginTop: 40,
    backgroundColor: '#EEEEEE',
  },

  //containerStatus
  containerStatus: {
    backgroundColor: 'red',
    height: 20,
    marginBottom: 20,
    borderRadius: 20,
  },

  //Status Text
  statusText: {
    color: 'white',
    flexDirection: 'row',
    textAlign: 'center',
  },

  //containerList
  containerList: {
    paddingLeft: 10,
    paddingRight: 10,
  },

  //Separator - Add form/List
  separator: {
    height: 0,
    backgroundColor: 'aliceblue',
  },
});

export default RealTimeAddUpdateUser;

pages/HomeScreen.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/

import { gt } from 'lodash';
import React from 'react';
import {View, Text, SafeAreaView} from 'react-native';
import Mybutton from './components/Mybutton';
import Mytext from './components/Mytext';
import db from './config';

const HomeScreen = ({navigation}) => {
  return (
    <SafeAreaView style={{flex: 1}}>
      <View
        style={{
          flex: 1,
          backgroundColor: 'white',
          padding: 16
        }}>
        <View style={{flex: 1}}>
          <Mytext
            text="Offline First App in React Native
                  using PouchDB and CouchDB"
          />
          <Mybutton
            title="Register User (Add Data)"
            customClick={() => navigation.navigate('Register')}
          />
          <Mybutton
            title="Update User (Update Data)"
            customClick={() => navigation.navigate('Update')}
          />
          <Mybutton
            title="View User (Get Single Record, filtered)"
            customClick={() => navigation.navigate('View')}
          />
          <Mybutton
            title="View All (Get All Records)"
            customClick={() => navigation.navigate('ViewAll')}
          />
          <Mybutton
            title="Delete (Delete Single Record)"
            customClick={() => navigation.navigate('Delete')}
          />
          <Mybutton
            title="RealTime Record Add/Remove"
            customClick={() =>
              navigation.navigate('RealTimeAddUpdate')
            }
          />
        </View>
        <Text
          style={{
            fontSize: 18,
            textAlign: 'center',
            color: 'grey'
          }}>
          Offline First App in React Native{' '}
          using PouchDB and CouchDB
        </Text>
        <Text
          style={{
            fontSize: 16,
            textAlign: 'center',
            color: 'grey'
          }}>
          www.aboutreact.com
        </Text>
      </View>
    </SafeAreaView>
  );
};

export default HomeScreen;

pages/RegisterUser.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Screen to register the user

import React, {useEffect, useState} from 'react';
import {
  View,
  ScrollView,
  KeyboardAvoidingView,
  Alert,
  SafeAreaView,
  Text,
} from 'react-native';
import Mytextinput from './components/Mytextinput';
import Mybutton from './components/Mybutton';
import db from './config';

const RegisterUser = ({navigation}) => {
  let [userName, setUserName] = useState('');
  let [userContact, setUserContact] = useState('');
  let [userAddress, setUserAddress] = useState('');
  let nextId = 1;

  useEffect(() => {
    db.allDocs({include_docs: true}).then((results) => {
      console.log(results);
      nextId = results.total_rows + 1;
      console.log('nextId', nextId);
    });
  }, []);

  let register_user = () => {
    console.log(userName, userContact, userAddress);

    if (!userName) {
      alert('Please fill name');
      return;
    }
    if (!userContact) {
      alert('Please fill Contact Number');
      return;
    }
    if (!userAddress) {
      alert('Please fill Address');
      return;
    }

    db.post({
      name: userName,
      contact: userContact,
      address: userAddress,
    })
      .then((doc) => {
        console.log('doc', doc);
        if (!doc.ok) {
          alert('Registration Failed');
          return;
        }
        Alert.alert(
          'Success',
          'You are Registered Successfully',
          [
            {
              text: 'Ok',
              onPress: () => navigation.navigate('HomeScreen'),
            },
          ],
          {cancelable: false},
        );
      })
      .catch((error) => alert('Error Inserting -> ' + error));
  };

  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={{flex: 1, backgroundColor: 'white', padding: 16}}>
        <View style={{flex: 1}}>
          <ScrollView keyboardShouldPersistTaps="handled">
            <KeyboardAvoidingView
              behavior="padding"
              style={{flex: 1, justifyContent: 'space-between'}}>
              <Mytextinput
                placeholder="Enter Name"
                onChangeText={
                  (userName) => setUserName(userName)
                }
                style={{padding: 10}}
              />
              <Mytextinput
                placeholder="Enter Contact No"
                onChangeText={
                  (userContact) => setUserContact(userContact)
                }
                maxLength={10}
                keyboardType="numeric"
                style={{padding: 10}}
              />
              <Mytextinput
                placeholder="Enter Address"
                onChangeText={
                  (userAddress) => setUserAddress(userAddress)
                }
                maxLength={225}
                numberOfLines={5}
                multiline={true}
                style={{textAlignVertical: 'top', padding: 10}}
              />
              <Mybutton title="Submit" customClick={register_user} />
            </KeyboardAvoidingView>
          </ScrollView>
        </View>
        <Text
          style={{
            fontSize: 18,
            textAlign: 'center',
            color: 'grey'
          }}>
          Offline First App in React Native{' '}
          using PouchDB and CouchDB
        </Text>
        <Text
          style={{
            fontSize: 16,
            textAlign: 'center',
            color: 'grey'
          }}>
          www.aboutreact.com
        </Text>
      </View>
    </SafeAreaView>
  );
};

export default RegisterUser;

pages/UpdateUser.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Screen to update the user

import React, {useState} from 'react';
import {
  View,
  ScrollView,
  KeyboardAvoidingView,
  Alert,
  SafeAreaView,
  Text,
} from 'react-native';
import Mytextinput from './components/Mytextinput';
import Mybutton from './components/Mybutton';
import db from './config';

const UpdateUser = ({navigation}) => {
  let [inputUserId, setInputUserId] = useState('');
  let [userName, setUserName] = useState('');
  let [userContact, setUserContact] = useState('');
  let [userAddress, setUserAddress] = useState('');

  let updateAllStates = (name, contact, address) => {
    setUserName(name);
    setUserContact(contact);
    setUserAddress(address);
  };

  let searchUser = () => {
    console.log(inputUserId);
    db.get(inputUserId)
      .then((doc) => {
        console.log(doc);
        updateAllStates(doc.name, doc.contact, doc.address);
      })
      .catch((err) => {
        alert('No user found');
        updateAllStates('', '', '');
      });
  };
  let updateUser = () => {
    console.log(inputUserId, userName, userContact, userAddress);

    if (!inputUserId) {
      alert('Please fill User id');
      return;
    }
    if (!userName) {
      alert('Please fill name');
      return;
    }
    if (!userContact) {
      alert('Please fill Contact Number');
      return;
    }
    if (!userAddress) {
      alert('Please fill Address');
      return;
    }

    db.post({
      // You can also set current date for the complex unique id
      // _id: Date.now().toString(),
      name: userName,
      contact: userContact,
      address: userAddress,
    })
      .then((doc) => {
        console.log('doc', doc);
        if (!doc.ok) {
          alert('Registration Failed');
          return;
        }
        Alert.alert(
          'Success',
          'User updated successfully',
          [
            {
              text: 'Ok',
              onPress: () => navigation.navigate('HomeScreen'),
            },
          ],
          {cancelable: false},
        );
      })
      .catch((error) => alert('Updation Failed -> ' + error));
  };

  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={{flex: 1, backgroundColor: 'white', padding: 16}}>
        <View style={{flex: 1}}>
          <ScrollView keyboardShouldPersistTaps="handled">
            <KeyboardAvoidingView
              behavior="padding"
              style={{flex: 1, justifyContent: 'space-between'}}>
              <Mytextinput
                placeholder="Enter User Id"
                style={{padding: 10}}
                onChangeText={
                  (inputUserId) => setInputUserId(inputUserId)
                }
              />
              <Mybutton
                title="Search User"
                customClick={searchUser}
              />
              <Mytextinput
                placeholder="Enter Name"
                value={userName}
                style={{padding: 10}}
                onChangeText={
                  (userName) => setUserName(userName)
                }
              />
              <Mytextinput
                placeholder="Enter Contact No"
                value={'' + userContact}
                onChangeText={
                  (userContact) => setUserContact(userContact)
                }
                maxLength={10}
                style={{padding: 10}}
                keyboardType="numeric"
              />
              <Mytextinput
                value={userAddress}
                placeholder="Enter Address"
                onChangeText={
                  (userAddress) => setUserAddress(userAddress)
                }
                maxLength={225}
                numberOfLines={5}
                multiline={true}
                style={{textAlignVertical: 'top', padding: 10}}
              />
              <Mybutton
                title="Update User"
                customClick={updateUser}
              />
            </KeyboardAvoidingView>
          </ScrollView>
        </View>
        <Text
          style={{
            fontSize: 18,
            textAlign: 'center',
            color: 'grey'
          }}>
          Offline First App in React Native{' '}
          using PouchDB and CouchDB
        </Text>
        <Text
          style={{
            fontSize: 16,
            textAlign: 'center',
            color: 'grey'
          }}>
          www.aboutreact.com
        </Text>
      </View>
    </SafeAreaView>
  );
};

export default UpdateUser;

pages/ViewAllUser.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Screen to view all the user*/

import React, {useState, useEffect} from 'react';
import {FlatList, Text, View, SafeAreaView} from 'react-native';
import db from './config';

const ViewAllUser = () => {
  let [flatListItems, setFlatListItems] = useState([]);

  useEffect(() => {
    db.allDocs({include_docs: true, descending: true})
      .then((results) => {
        let temp = results.rows.map((row) => row.doc);
        console.log('temp', temp);
        setFlatListItems(temp);
      })
      .catch((err) => alert('Unable to get data'));
  }, []);

  let listViewItemSeparator = () => {
    return <View style={{height: 20}} />;
  };

  let listItemView = (item) => {
    return (
      <View
        key={item._id}
        style={{
          backgroundColor: 'white',
          padding: 16,
        }}>
        <Text>Id: {item._id}</Text>
        <Text>Name: {item.name}</Text>
        <Text>Contact: {item.contact}</Text>
        <Text>Address: {item.address}</Text>
      </View>
    );
  };

  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={{flex: 1, padding: 16}}>
        <FlatList
          data={flatListItems}
          ItemSeparatorComponent={listViewItemSeparator}
          keyExtractor={(item, index) => index.toString()}
          renderItem={({item}) => listItemView(item)}
        />
      </View>
    </SafeAreaView>
  );
};

export default ViewAllUser;

ViewUser.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Screen to view single user

import React, {useState} from 'react';
import {Text, View, SafeAreaView} from 'react-native';
import Mytextinput from './components/Mytextinput';
import Mybutton from './components/Mybutton';
import db from './config';

const ViewUser = () => {
  let [inputUserId, setInputUserId] = useState('');
  let [userData, setUserData] = useState({});

  let searchUser = () => {
    console.log(inputUserId);
    setUserData({});
    db.get(inputUserId)
      .then((doc) => {
        console.log(doc);
        setUserData(doc);
      })
      .catch((err) => {
        alert('No user found');
        updateAllStates('', '', '');
      });
  };

  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={{flex: 1, backgroundColor: 'white', padding: 16}}>
        <View style={{flex: 1}}>
          <Mytextinput
            placeholder="Enter User Id"
            onChangeText={
              (inputUserId) => setInputUserId(inputUserId)
            }
            style={{padding: 10}}
          />
          <Mybutton title="Search User" customClick={searchUser} />
          <View style={{marginTop: 16}}>
            <Text>User Id: {userData._id}</Text>
            <Text>User Name: {userData.name}</Text>
            <Text>User Contact: {userData.contact}</Text>
            <Text>User Address: {userData.address}</Text>
          </View>
        </View>
        <Text
          style={{
            fontSize: 18,
            textAlign: 'center',
            color: 'grey'
          }}>
          Offline First App in React Native{' '}
          using PouchDB and CouchDB
        </Text>
        <Text
          style={{
            fontSize: 16,
            textAlign: 'center',
            color: 'grey'
          }}>
          www.aboutreact.com
        </Text>
      </View>
    </SafeAreaView>
  );
};

export default ViewUser;

pages/DeleteUser.js

// Building Offline First App in React Native using PouchDB and CouchDB
// https://aboutreact.com/react-native-offline-app-using-pouchdb-couchdb/
// Screen to delete the user

import React, {useState} from 'react';
import {Text, View, Alert, SafeAreaView} from 'react-native';
import Mytextinput from './components/Mytextinput';
import Mybutton from './components/Mybutton';
import db from './config';

const DeleteUser = ({navigation}) => {
  let [inputUserId, setInputUserId] = useState('');

  let deleteUser = () => {
    db.get(inputUserId)
      .then((doc) => {
        return db.remove(doc).then((doc) => {
          console.log('doc', doc);
          if (!doc.ok) {
            alert('Deletion Failed');
            return;
          }
          Alert.alert(
            'Success',
            'User Deleted successfully',
            [
              {
                text: 'Ok',
                onPress: () => navigation.navigate('HomeScreen'),
              },
            ],
            {cancelable: false},
          );
        });
      })
      .catch((err) => {
        alert('No user found with inserted id');
      });
  };

  return (
    <SafeAreaView style={{flex: 1}}>
      <View style={{flex: 1, backgroundColor: 'white', padding: 16}}>
        <View style={{flex: 1}}>
          <Mytextinput
            placeholder="Enter User Id"
            onChangeText={
              (inputUserId) => setInputUserId(inputUserId)
            }
            style={{padding: 10}}
          />
          <Mybutton title="Delete User" customClick={deleteUser} />
        </View>
        <Text
          style={{
            fontSize: 18,
            textAlign: 'center',
            color: 'grey'
          }}>
          Offline First App in React Native{' '}
          using PouchDB and CouchDB
        </Text>
        <Text
          style={{
            fontSize: 16,
            textAlign: 'center',
            color: 'grey'
          }}>
          www.aboutreact.com
        </Text>
      </View>
    </SafeAreaView>
  );
};

export default DeleteUser;

To Run the React Native App

Open the terminal again and jump into your project using.

cd ProjectName

1. Start Metro Bundler

First, you will need to start Metro, the JavaScript bundler that ships with React Native. To start Metro bundler run following command

npx react-native start

Once you start Metro Bundler it will run forever on your terminal until you close it. Let Metro Bundler run in its own terminal. Open a new terminal and run the application.

2. Start React Native Application

To run the project on an Android Virtual Device or on real debugging device

npx react-native run-android

or on the iOS Simulator by running (macOS only)

npx react-native run-ios

Output Screenshots

react_native_pouchdb_couchdb_example1   react_native_pouchdb_couchdb_example2   react_native_pouchdb_couchdb_example3   react_native_pouchdb_couchdb_example4   react_native_pouchdb_couchdb_example5   react_native_pouchdb_couchdb_example6   react_native_pouchdb_couchdb_example7   react_native_pouchdb_couchdb_example8   react_native_pouchdb_couchdb_example9   react_native_pouchdb_couchdb_example10 react_native_pouchdb_couchdb_example11

If you have any doubts or you want to share something about the topic you can comment below or contact us here. There will be more posts coming soon. Stay tuned!

Hope you liked it. 🙂

3 thoughts on “Building Offline First App in React Native using PouchDB and CouchDB”

  1. Hi there,

    Thanks for the nice tutorial. I have one question though, how can we prevent mobile users on rooted device seeing the password we use to connect to the actual db. I am having the same issue with firebase database as well. Do we really need one of our backend as proxy for authenticating before connecting to these databases?

    Reply
    • It is possible with the proxy server in Auth2.0 but Pouch + Couch db is designed to work offline which means more focused on offline functionality, conflict resolution and proper syncing of the data with minimum transactions so I don’t think they have any kind of Auth2.0 mechanism.
      Also I think introducing new proxy server will increase the complexity and instead of creating something awesome developer will put his/her efforts in managing the servers and things.

      If we talk about the solution, I have done something similar for native Android app to secure my password where I was getting the details of the device on the App launch and if found everything normal then calling an API to get the creds which I was storing in local storage and with the help of that creds I was calling the API. I am not sure about the React Native but you can use react-native-is-device-rooted to do the same.
      PS: You will still need an external API to get the creds which is insecure way to get the creds without authentication.

      Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.