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?
We all live in a hyper-connected world but there are so many time when we loose the mobile connectivity. This mobile connection issue can trusted your application user if your application works online. An app which communicates with the server can provide so many features to the user but in case of network connection issue it also provide bad user experience.
In the above scenario as an app developer we can not do anything about the network issue but what we can do is to change our approach to deliver an offline first app which can provide the same user experience in offline mode which we can provide online.
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.
Issue with “Offline First” Approach?
Theoretically offline first concept is very good but building the same application is a bit complex process.
- How to sync data to the local db from server?
- What about syncing the local data back to the server, who will do that perfectly?
- What if anyone else updated the same record which you have updated in the offline mode, Who will resolve the conflict?
- 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.
- 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. - 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.
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
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
- In case of any change in CouchDB URL please update the remoteDB URL (http://localhost:5984/<db> in below code)
- Update the username(admin) and password(admin) which you have created while setting up the CouchDB
- 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
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. 🙂
Great Tutorial Thanks a lot Bro!
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?
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.