React Native – Local Database/SQLite
In this post, we will see how to work with Local Database in React Native App. As a Mobile Application developer, we always search for a Local Database. A database that offers offline synchronization, reliable performance, security, and better integration with other stacks.
SQLite Database
SQLite is an open-source SQL database that stores data to a text file on a device. It supports all the relational database features. In order to access this database, we don’t need to establish any kind of connections for it like JDBC, ODBC. We only have to define the SQL statements for creating and updating the database. Access to it involves accessing the file system. This can be slow. Therefore, it is recommended to perform database operations asynchronously.
Add Navigation Header and required Screen
Firstly we create a normal React Native App.After that we will add the Navigation Header (react-navigation and react-native-gesture-handler) and Home Screen for your app. So, it will look like the Native App. In the terminal or command line, type this command to install React Navigation module and don’t forget to stop the running Metro Bundler before installing the modules.
npm install react-navigation --save
npm install react-native-gesture-handler --save
react-native link react-native-gesture-handler
Next, create a folder for components and components files in the root of the app folder.
Create a folder - components
Add components/ProductScreen.js
Add components/ProductDetailsScreen.js
Add components/ProductAddScreen.js
Add components/ProductEditScreen.js
Open and edit `components/ProductScreen.js` then add this React codes.
import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';
export default class ProductScreen extends Component {
static navigationOptions = {
title: 'Product List',
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Product List</Text>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('ProductDetails')}
/>
<Button
title="Go to Add Product"
onPress={() => this.props.navigation.navigate('AddProduct')}
/>
<Button
title="Go to Edit Product"
onPress={() => this.props.navigation.navigate('EditProduct')}
/>
</View>
);
}
}
Open and edit `components/ProductDetailsScreen.js` then add this React codes.
import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';
export default class ProductDetailsScreen extends Component {
static navigationOptions = {
title: 'Product Details',
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Product Details</Text>
<Button
title="Go to Details... again"
onPress={() => this.props.navigation.push('ProductDetails')}
/>
<Button
title="Go to Home"
onPress={() => this.props.navigation.navigate('Product')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
Open and edit `components/ProductAddScreen.js` then add this React codes.
import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';
export default class ProductAddScreen extends Component {
static navigationOptions = {
title: 'Add Product',
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Add Product</Text>
<Button
title="Go to Add Product... again"
onPress={() => this.props.navigation.push('AddProduct')}
/>
<Button
title="Go to Home"
onPress={() => this.props.navigation.navigate('Product')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
Open and edit `components/ProductEditScreen.js` then add this React codes.
import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';
export default class ProductEditScreen extends Component {
static navigationOptions = {
title: 'Edit Product',
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Add Product</Text>
<Button
title="Go to Edit Product... again"
onPress={() => this.props.navigation.push('EditProduct')}
/>
<Button
title="Go to Home"
onPress={() => this.props.navigation.navigate('Product')}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
Next, open and edit `App.js` then add replace all codes with this.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { createAppContainer, createStackNavigator } from 'react-navigation';
import ProductScreen from './components/ProductScreen';
import ProductDetailsScreen from './components/ProductDetailsScreen';
import ProductAddScreen from './components/ProductAddScreen';
import ProductEditScreen from './components/ProductEditScreen';
const RootStack = createStackNavigator(
{
Product: ProductScreen,
ProductDetails: ProductDetailsScreen,
AddProduct: ProductAddScreen,
EditProduct: ProductEditScreen,
},
{
initialRouteName: 'Product',
navigationOptions: {
headerStyle: {
backgroundColor: '#777777',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
},
);
const RootContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return <RootContainer />;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
After Re-run the React Native app on the iOS/Android Device/Simulator you will see this updated views.
Install and Configure React Native SQLite Storage
Before creating an offline CRUD application using local data, we have to install the `react-native-sqlite-storage` and required UI/UX module.
npm install react-native-sqlite-storage --save
npm install react-native-elements --save
react-native link
We will use separate Class for accessing SQLite and do some CRUD (create, read, update, delete) operations. For that, create a new Javascript file on the root of the project folder.
Add Database.js
Open and edit `Database.js` then add this SQLite import with the configuration.
import SQLite from "react-native-sqlite-storage";
SQLite.DEBUG(true);
SQLite.enablePromise(true);
Add the constant variables after that.
const database_name = "Reactoffline.db";
const database_version = "1.0";
const database_displayname = "SQLite React Offline Database";
const database_size = 200000;
Give this file a class name.
export default class Database {
}
Inside the class bracket, add a function for Database initialization that creates Database, tables, etc.
initDB() {
let db;
return new Promise((resolve) => {
console.log("Plugin integrity check ...");
SQLite.echoTest()
.then(() => {
console.log("Integrity check passed ...");
console.log("Opening database ...");
SQLite.openDatabase(
database_name,
database_version,
database_displayname,
database_size
)
.then(DB => {
db = DB;
console.log("Database OPEN");
db.executeSql('SELECT 1 FROM Product LIMIT 1').then(() => {
console.log("Database is ready ... executing query ...");
}).catch((error) =>{
console.log("Received error: ", error);
console.log("Database not yet ready ... populating data");
db.transaction((tx) => {
tx.executeSql('CREATE TABLE IF NOT EXISTS Product (prodId, prodName, prodDesc, prodImage, prodPrice)');
}).then(() => {
console.log("Table created successfully");
}).catch(error => {
console.log(error);
});
});
resolve(db);
})
.catch(error => {
console.log(error);
});
})
.catch(error => {
console.log("echoTest failed - plugin not functional");
});
});
};
Add a function for the close Database connection.
closeDatabase(db) {
if (db) {
console.log("Closing DB");
db.close()
.then(status => {
console.log("Database CLOSED");
})
.catch(error => {
this.errorCB(error);
});
} else {
console.log("Database was not OPENED");
}
};
Add a function to get the list of products.
listProduct() {
return new Promise((resolve) => {
const products = [];
this.initDB().then((db) => {
db.transaction((tx) => {
tx.executeSql('SELECT p.prodId, p.prodName, p.prodImage FROM Product p', []).then(([tx,results]) => {
console.log("Query completed");
var len = results.rows.length;
for (let i = 0; i < len; i++) {
let row = results.rows.item(i);
console.log(`Prod ID: ${row.prodId}, Prod Name: ${row.prodName}`)
const { prodId, prodName, prodImage } = row;
products.push({
prodId,
prodName,
prodImage
});
}
console.log(products);
resolve(products);
});
}).then((result) => {
this.closeDatabase(db);
}).catch((err) => {
console.log(err);
});
}).catch((err) => {
console.log(err);
});
});
}
Add a function to get Product by ID.
productById(id) {
console.log(id);
return new Promise((resolve) => {
this.initDB().then((db) => {
db.transaction((tx) => {
tx.executeSql('SELECT * FROM Product WHERE prodId = ?', [id]).then(([tx,results]) => {
console.log(results);
if(results.rows.length > 0) {
let row = results.rows.item(0);
resolve(row);
}
});
}).then((result) => {
this.closeDatabase(db);
}).catch((err) => {
console.log(err);
});
}).catch((err) => {
console.log(err);
});
});
}
Add a function to save a new product to the SQLite database.
addProduct(prod) {
return new Promise((resolve) => {
this.initDB().then((db) => {
db.transaction((tx) => {
tx.executeSql('INSERT INTO Product VALUES (?, ?, ?, ?, ?)', [prod.prodId, prod.prodName, prod.prodDesc, prod.prodImage, prod.prodPrice]).then(([tx, results]) => {
resolve(results);
});
}).then((result) => {
this.closeDatabase(db);
}).catch((err) => {
console.log(err);
});
}).catch((err) => {
console.log(err);
});
});
}
Add a function to update a product.
updateProduct(id, prod) {
return new Promise((resolve) => {
this.initDB().then((db) => {
db.transaction((tx) => {
tx.executeSql('UPDATE Product SET prodName = ?, prodDesc = ?, prodImage = ?, prodPrice = ? WHERE prodId = ?', [prod.prodName, prod.prodDesc, prod.prodImage, prod.prodPrice, id]).then(([tx, results]) => {
resolve(results);
});
}).then((result) => {
this.closeDatabase(db);
}).catch((err) => {
console.log(err);
});
}).catch((err) => {
console.log(err);
});
});
}
Add a function to delete a product.
deleteProduct(id) {
return new Promise((resolve) => {
this.initDB().then((db) => {
db.transaction((tx) => {
tx.executeSql('DELETE FROM Product WHERE prodId = ?', [id]).then(([tx, results]) => {
console.log(results);
resolve(results);
});
}).then((result) => {
this.closeDatabase(db);
}).catch((err) => {
console.log(err);
});
}).catch((err) => {
console.log(err);
});
});
}
Show List of Product
To show or display the list of product, open and edit `components/ProductScreen.js` then replace all imports with these imports of StyleSheet, FlatList, ActivityIndicator, View, Text (react-native), ListItem, Button (react-native-elements), and Database (SQLite).
import React, { Component } from 'react';
import { StyleSheet, FlatList, ActivityIndicator, View, Text } from 'react-native';
import { ListItem, Button } from 'react-native-elements';
import Database from '../Database';
Instantiate the Database as a constant variable before the class name.
const db = new Database();
Next, replace `navigationOptions` with these.
static navigationOptions = ({ navigation }) => {
return {
title: 'Product List',
headerRight: (
<Button
buttonStyle={{ padding: 0, backgroundColor: 'transparent' }}
icon={{ name: 'add-circle', style: { marginRight: 0, fontSize: 28 } }}
onPress={() => {
navigation.navigate('AddProduct', {
onNavigateBack: this.handleOnNavigateBack
});
}}
/>
),
};
};
Add a constructor function.
constructor() {
super();
this.state = {
isLoading: true,
products: [],
notFound: 'Products not found.\nPlease click (+) button to add it.'
};
}
Add a function to initialize the screen.
componentDidMount() {
this._subscribe = this.props.navigation.addListener('didFocus', () => {
this.getProducts();
});
}
Add a function to get the product list from Database class.
getProducts() {
let products = [];
db.listProduct().then((data) => {
products = data;
this.setState({
products,
isLoading: false,
});
}).catch((err) => {
console.log(err);
this.setState = {
isLoading: false
}
})
}
Add a variable to iterate the listed product in the view.
keyExtractor = (item, index) => index.toString()
Add a function to render the List Item.
renderItem = ({ item }) => (
<ListItem
title={item.prodName}
leftAvatar={{
source: item.prodImage && { uri: item.prodImage },
title: item.prodName[0]
}}
onPress={() => {
this.props.navigation.navigate('ProductDetails', {
prodId: `${item.prodId}`,
});
}}
chevron
bottomDivider
/>
)
Add a function to render the rest of List view.
render() {
if(this.state.isLoading){
return(
<View style={styles.activity}>
<ActivityIndicator size="large" color="#0000ff"/>
</View>
)
}
if(this.state.products.length === 0){
return(
<View>
<Text style={styles.message}>{this.state.notFound}</Text>
</View>
)
}
return (
<FlatList
keyExtractor={this.keyExtractor}
data={this.state.products}
renderItem={this.renderItem}
/>
);
}
Finally, add a stylesheet for the whole screen after the class bracket.
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: 22
},
item: {
padding: 10,
fontSize: 18,
height: 44,
},
activity: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
message: {
padding: 16,
fontSize: 18,
color: 'red'
}
});
Show Product Details and Delete Product
From the list of product view, you will see that list item has an action button to show the product details. Next, open and edit `components/ProductDetailsScreen.js` then replace the imports with these imports of ScrollView, StyleSheet, Image, ActivityIndicator, View, Text (react-native), Card, Button (react-native-elements), and Database (SQLite).
import React, { Component } from 'react';
import { ScrollView, StyleSheet, Image, ActivityIndicator, View, Text } from 'react-native';
import { Card, Button } from 'react-native-elements';
import Database from '../Database';
Instantiate the Database as a constant variable.
const db = new Database();
Add a function as the constructor.
constructor() {
super();
this.state = {
isLoading: true,
product: {},
id: '',
};
}
Add a function to initialize the screen.
componentDidMount() {
this._subscribe = this.props.navigation.addListener('didFocus', () => {
const { navigation } = this.props;
db.productById(navigation.getParam('prodId')).then((data) => {
console.log(data);
product = data;
this.setState({
product,
isLoading: false,
id: product.prodId
});
}).catch((err) => {
console.log(err);
this.setState = {
isLoading: false
}
})
});
}
Add a function to delete a product data.
deleteProduct(id) {
const { navigation } = this.props;
this.setState({
isLoading: true
});
db.deleteProduct(id).then((result) => {
console.log(result);
this.props.navigation.goBack();
}).catch((err) => {
console.log(err);
this.setState = {
isLoading: false
}
})
}
Add a function to render the whole Product Details view.
render() {
if(this.state.isLoading){
return(
<View style={styles.activity}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
)
}
return (
<ScrollView>
<Card style={styles.container}>
<View style={styles.subContainer}>
<View>
<Image
style={{width: 150, height: 150}}
source={{uri: this.state.product.prodImage}}
/>
</View>
<View>
<Text style={{fontSize: 16}}>Product ID: {this.state.product.prodId}</Text>
</View>
<View>
<Text style={{fontSize: 16}}>Product Name: {this.state.product.prodName}</Text>
</View>
<View>
<Text style={{fontSize: 16}}>Product Desc: {this.state.product.prodDesc}</Text>
</View>
<View>
<Text style={{fontSize: 16}}>Product Price: {this.state.product.prodPrice}</Text>
</View>
</View>
<View style={styles.detailButton}>
<Button
large
backgroundColor={'#CCCCCC'}
leftIcon={{name: 'edit'}}
title='Edit'
onPress={() => {
this.props.navigation.navigate('EditProduct', {
prodId: `${this.state.id}`,
});
}} />
</View>
<View style={styles.detailButton}>
<Button
large
backgroundColor={'#999999'}
color={'#FFFFFF'}
leftIcon={{name: 'delete'}}
title='Delete'
onPress={() => this.deleteProduct(this.state.id)} />
</View>
</Card>
</ScrollView>
);
}
Finally, add the stylesheet for this screen after the class bracket.
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20
},
subContainer: {
flex: 1,
paddingBottom: 20,
borderBottomWidth: 2,
borderBottomColor: '#CCCCCC',
},
activity: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
detailButton: {
marginTop: 10
}
})
Add Product
To add or save a new Product, open and edit the `components/ProductAddScreen.js` then replace all imports with these imports of StyleSheet, ScrollView, ActivityIndicator, View, TextInput (react-native), Button (react-native-elements), and Database (SQLite).
import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput } from 'react-native';
import { Button } from 'react-native-elements';
import Database from '../Database';
Instantiate the Database as a constant variable.
const db = new Database();
Add a constructor inside the class bracket after the `navigationOptions`.
constructor() {
super();
this.state = {
prodId: '',
prodName: '',
prodDesc: '',
prodImage: '',
prodPrice: '0',
isLoading: false,
};
}
Add a function to update the input text values.
updateTextInput = (text, field) => {
const state = this.state
state[field] = text;
this.setState(state);
}
Add a function to save a product to the SQLite table.
saveProduct() {
this.setState({
isLoading: true,
});
let data = {
prodId: this.state.prodId,
prodName: this.state.prodName,
prodDesc: this.state.prodDesc,
prodImage: this.state.prodImage,
prodPrice: this.state.prodPrice
}
db.addProduct(data).then((result) => {
console.log(result);
this.setState({
isLoading: false,
});
this.props.navigation.state.params.onNavigateBack;
this.props.navigation.goBack();
}).catch((err) => {
console.log(err);
this.setState({
isLoading: false,
});
})
}
Add a function to render the whole add product view.
render() {
if(this.state.isLoading){
return(
<View style={styles.activity}>
<ActivityIndicator size="large" color="#0000ff"/>
</View>
)
}
return (
<ScrollView style={styles.container}>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product ID'}
value={this.state.prodId}
onChangeText={(text) => this.updateTextInput(text, 'prodId')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product Name'}
value={this.state.prodName}
onChangeText={(text) => this.updateTextInput(text, 'prodName')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
multiline={true}
numberOfLines={4}
placeholder={'Product Description'}
value={this.state.prodDesc}
onChangeText={(text) => this.updateTextInput(text, 'prodDesc')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product Image'}
value={this.state.prodImage}
onChangeText={(text) => this.updateTextInput(text, 'prodImage')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product Price'}
value={this.state.prodPrice}
keyboardType='numeric'
onChangeText={(text) => this.updateTextInput(text, 'prodPrice')}
/>
</View>
<View style={styles.button}>
<Button
large
leftIcon={{name: 'save'}}
title='Save'
onPress={() => this.saveProduct()} />
</View>
</ScrollView>
);
}
Finally, add the style for the whole screen.
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20
},
subContainer: {
flex: 1,
marginBottom: 20,
padding: 5,
borderBottomWidth: 2,
borderBottomColor: '#CCCCCC',
},
activity: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
}
})
Edit Product
To edit a product, open and edit `components/ProductEditScreen.js` then replace all imports with these imports of StyleSheet, ScrollView, ActivityIndicator, View, TextInput (react-native), Button (react-native-elements), and Database (SQLite).
import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput } from 'react-native';
import { Button } from 'react-native-elements';
import Database from '../Database';
Instantiate the Database as a constant variable.
const db = new Database();
Add the constructor after the `navigationOptions` function.
constructor() {
super();
this.state = {
prodId: '',
prodName: '',
prodDesc: '',
prodImage: '',
prodPrice: '0',
isLoading: true,
};
}
Add a function to initialize the screen that will get product data.
componentDidMount() {
const { navigation } = this.props;
db.productById(navigation.getParam('prodId')).then((data) => {
console.log(data);
const product = data;
this.setState({
prodId: product.prodId,
prodName: product.prodName,
prodDesc: product.prodDesc,
prodImage: product.prodImage,
prodPrice: product.prodPrice,
isLoading: false,
});
}).catch((err) => {
console.log(err);
this.setState = {
isLoading: false
}
})
}
Add a function to update the input text value.
updateTextInput = (text, field) => {
const state = this.state
state[field] = text;
this.setState(state);
}
Add a function to update the product data.
updateProduct() {
this.setState({
isLoading: true,
});
const { navigation } = this.props;
let data = {
prodId: this.state.prodId,
prodName: this.state.prodName,
prodDesc: this.state.prodDesc,
prodImage: this.state.prodImage,
prodPrice: this.state.prodPrice
}
db.updateProduct(data.prodId, data).then((result) => {
console.log(result);
this.setState({
isLoading: false,
});
this.props.navigation.state.params.onNavigateBack;
this.props.navigation.goBack();
}).catch((err) => {
console.log(err);
this.setState({
isLoading: false,
});
})
}
Add a function to render the whole Edit Product screen.
render() {
if(this.state.isLoading){
return(
<View style={styles.activity}>
<ActivityIndicator size="large" color="#0000ff"/>
</View>
)
}
return (
<ScrollView style={styles.container}>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product ID'}
value={this.state.prodId}
onChangeText={(text) => this.updateTextInput(text, 'prodId')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product Name'}
value={this.state.prodName}
onChangeText={(text) => this.updateTextInput(text, 'prodName')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
multiline={true}
numberOfLines={4}
placeholder={'Product Description'}
value={this.state.prodDesc}
onChangeText={(text) => this.updateTextInput(text, 'prodDesc')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product Image'}
value={this.state.prodImage}
onChangeText={(text) => this.updateTextInput(text, 'prodImage')}
/>
</View>
<View style={styles.subContainer}>
<TextInput
placeholder={'Product Price'}
value={this.state.prodPrice}
keyboardType='numeric'
onChangeText={(text) => this.updateTextInput(text, 'prodPrice')}
/>
</View>
<View style={styles.button}>
<Button
large
leftIcon={{name: 'save'}}
title='Save'
onPress={() => this.updateProduct()} />
</View>
</ScrollView>
);
}
Finally, add the stylesheet after the class bracket.
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20
},
subContainer: {
flex: 1,
marginBottom: 20,
padding: 5,
borderBottomWidth: 2,
borderBottomColor: '#CCCCCC',
},
activity: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
}
})
Run and Test React Native and SQLite Offline Mobile App
As we show you at the first step, run the React Native and SQLite app using this command.
react-native run-android
react-native run-ios
After the new terminal window open, just go to the project folder then run this command.
react-native start
Now, you will see the whole application in the Android/iOS Device.