It is quite common for developers to use SQLite, a C language library, as a data store for mobile applications. SQLite is especially useful for offline applications, and many platforms include support for SQLite so that it can be installed directly.

In this article, we’ll use SQLite with React Native, build a simple to-do list application, and show us how all CRUD operations work. We will continue to use TypeScript because of its code quality and maintainability.

Prerequisites.

  • React and React Native basic understanding
  • Be familiar with the TypeScript

Start to work

We will create a to-do list application that includes the following.

  • Finish button: Clears completed items
  • Add ToDo button: Add a new project
  • twouseState: One to keep a to-do list and one to keep track of new to-do items
  • Application component: Handles user events, such as adding and removing to-do list items
  • Dumb component: Displays to-do list items

Note that we will use a set of functional components and several new hook apis to implement state management.

Configure React Native and TypeScript

We’ll start by creating a React Native application using TypeScript.

npx react-native init MyApp --template react-native-template-typescript
Copy the code

You can clone the React app and do it while reading the article.

You will see that there are two branches in the repository, start and Main. We’ll start with the start branch.

The introduction of SQLite

Let’s introduce SQLite to our application. In order to connect with SQLite, we will use [react – native – SQLite – storage] (https://www.npmjs.com/package/react-native-sqlite-storage).

To install SQLite, run the following code on your terminal.

npm install --save react-native-sqlite-storage
Copy the code

Install the React Native package

iOS

If you are using iOS, run the following command to install the necessary React Native package.

cd ios && pod install && cd ..
Copy the code

If you’re running React Native 0.59 or lower, you have two options to install the React Native package, depending on whether you use CocoaPods.

Use CocoaPods.

If you are running CocoaPods, add the following code to your podfile.

pod 'React', :path => '.. /node_modules/react-native' pod 'react-native-sqlite-storage', :path => '.. /node_modules/react-native-sqlite-storage'Copy the code

Run POD Install or POD Update.

There is no CocoaPods

If you are not running CocoaPods, you must use the React-Native link. If you encounter any errors, you will have to open the project from Xcode and manually add dependencies. Please refer to the library documentation for more details.

The android system

If you use SQLite for your device in React Native.60 or above, you don’t need to take any additional steps.

However, if you’re using SQLite bundled with the react-native-sqlite-storage library, you can add the following code to your react-native-config.js file.

module.exports = { ... , dependencies: { ... , "react-native-sqlite-storage": { platforms: { android: { sourceDir: ".. /node_modules/react-native-sqlite-storage/platforms/android-native", packageImportPath: "import io.liteglue.SQLitePluginPackage;" , packageInstance: "new SQLitePluginPackage()" } } } ... }... };Copy the code

If you are running an older version of React Native, you must manually update Gradle files. Refer to the library’s documentation for complete configuration.

Implement a data storage service

Now we are ready to implement a data storage service. We will introduce a new.ts file called db-service.ts where we can add all our DB operations. First, let’s create a method to get a DB connection.

Since we are using TypeScript, we can install @types/react-native sqlite-storage to use the included types. If you insist on using JavaScript, you do not need to install this library.

Add db connection methods using the following code.

import {openDatabase} from 'react-native-sqlite-storage';

export const getDBConnection = async () => {
  return openDatabase({name: 'todo-data.db', location: 'default'});
};

Copy the code

If a table does not already exist when we start the application, we need to create one. Run the following code to add another method.

export const createTable = async (db: SQLiteDatabase) => { // create table if not exists const query = `CREATE TABLE IF NOT EXISTS ${tableName}( value TEXT NOT NULL ); `; await db.executeSql(query); };Copy the code

Since we use a promise-based API in our library, it is important to add the following code to our db-service.ts file.

enablePromise(true);

Copy the code

Next, we’ll add methods to save, delete, and retrieve our to-do items. After adding these methods, our DB service file will look like the following code block.

import { enablePromise, openDatabase, SQLiteDatabase } from 'react-native-sqlite-storage'; import { ToDoItem } from '.. /models'; const tableName = 'todoData'; enablePromise(true); export const getDBConnection = async () => { return openDatabase({ name: 'todo-data.db', location: 'default' }); }; export const createTable = async (db: SQLiteDatabase) => { // create table if not exists const query = `CREATE TABLE IF NOT EXISTS ${tableName}( value TEXT NOT NULL ); `; await db.executeSql(query); }; export const getTodoItems = async (db: SQLiteDatabase): Promise<ToDoItem[]> => { try { const todoItems: ToDoItem[] = []; const results = await db.executeSql(`SELECT rowid as id,value FROM ${tableName}`); results.forEach(result => { for (let index = 0; index < result.rows.length; index++) { todoItems.push(result.rows.item(index)) } }); return todoItems; } catch (error) { console.error(error); throw Error('Failed to get todoItems !!! '); }}; export const saveTodoItems = async (db: SQLiteDatabase, todoItems: ToDoItem[]) => { const insertQuery = `INSERT OR REPLACE INTO ${tableName}(rowid, value) values` + todoItems.map(i => `(${i.id}, '${i.value}')`).join(','); return db.executeSql(insertQuery); }; export const deleteTodoItem = async (db: SQLiteDatabase, id: number) => { const deleteQuery = `DELETE from ${tableName} where rowid = ${id}`; await db.executeSql(deleteQuery); }; export const deleteTable = async (db: SQLiteDatabase) => { const query = `drop table ${tableName}`; await db.executeSql(query); };Copy the code

We have added a deleteTable method, which will be useful when we develop the application. Later, we’ll add a feature for the user to use the deleteTable method to remove all data.

We can use Rowid, which comes with SQLite, as the primary key. We’ve updated our to-do list to have an ID and a value instead of a simple string so that we can easily delete items.

Next, we’ll add a model for our ToDoItem type. Add the following code to another folder in.NET called Models: index.ts.

export type ToDoItem = {
  id: number;
  value: string;
};

Copy the code

usedbservice

We will use our DB service in app.tsx. Follow these four steps.

1.) update ToDoItem component and App component to use the new ToDoItem type 2.) load data from SQLite 3.) Save data to DB 4.) Update deleted items in it. db

First, let’s finish setting up db, and then we’ll look at the final results of the app.tsx and todoItem.tsx files.

Load the data

To load data in our application, we will use useEffect and useCallback hooks.

const loadDataCallback = useCallback(async () => { try { const initTodos = [{ id: 0, value: 'go to shop' }, { id: 1, value: 'eat at least a one healthy foods' }, { id: 2, value: 'Do some exercises' }]; const db = await getDBConnection(); await createTable(db); const storedTodoItems = await getTodoItems(db); if (storedTodoItems.length) { setTodos(storedTodoItems); } else { await saveTodoItems(db, initTodos); setTodos(initTodos); } } catch (error) { console.error(error); }} []); useEffect(() => { loadDataCallback(); }, [loadDataCallback]);Copy the code

In the code snippet above, we are getting data from db. If we store any to-do items, we initialize the application with those items. If not, we persist the initial values to DB and use the data to initialize the application.

Add a project

To add a to-do, run the following code.

const addTodo = async () => {
    if (!newTodo.trim()) return;
    try {
      const newTodos = [...todos, {
        id: todos.reduce((acc, cur) => {
          if (cur.id > acc.id) return cur;
          return acc;
        }).id + 1, value: newTodo
      }];
      setTodos(newTodos);
      const db = await getDBConnection();
      await saveTodoItems(db, newTodos);
      setNewTodo('');
    } catch (error) {
      console.error(error);
    }
  };

Copy the code

Delete an item

Finally, run the following code to delete a to-do item.

const deleteItem = async (id: number) => { try { const db = await getDBConnection(); await deleteTodoItem(db, id); todos.splice(id, 1); setTodos(todos.slice(0)); } catch (error) { console.error(error); }};Copy the code

Our final app.tsx file should look like the following code.

/** * Sample React Native App * https://github.com/facebook/react-native * * Generated with the TypeScript template * https://github.com/react-native-community/react-native-template-typescript * * @format */ import React, { useCallback, useEffect, useState } from 'react'; import { Button, SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, TextInput, useColorScheme, View, } from 'react-native'; import { ToDoItemComponent } from './components/ToDoItem'; import { ToDoItem } from './models'; import { getDBConnection, getTodoItems, saveTodoItems, createTable, clearTable, deleteTodoItem } from './services/db-service'; const App = () => { const isDarkMode = useColorScheme() === 'dark'; const [todos, setTodos] = useState<ToDoItem[]>([]); const [newTodo, setNewTodo] = useState(''); const loadDataCallback = useCallback(async () => { try { const initTodos = [{ id: 0, value: 'go to shop' }, { id: 1, value: 'eat at least a one healthy foods' }, { id: 2, value: 'Do some exercises' }]; const db = await getDBConnection(); await createTable(db); const storedTodoItems = await getTodoItems(db); if (storedTodoItems.length) { setTodos(storedTodoItems); } else { await saveTodoItems(db, initTodos); setTodos(initTodos); } } catch (error) { console.error(error); }} []); useEffect(() => { loadDataCallback(); }, [loadDataCallback]); const addTodo = async () => { if (! newTodo.trim()) return; try { const newTodos = [...todos, { id: todos.length ? todos.reduce((acc, cur) => { if (cur.id > acc.id) return cur; return acc; }).id + 1 : 0, value: newTodo }]; setTodos(newTodos); const db = await getDBConnection(); await saveTodoItems(db, newTodos); setNewTodo(''); } catch (error) { console.error(error); }}; const deleteItem = async (id: number) => { try { const db = await getDBConnection(); await deleteTodoItem(db, id); todos.splice(id, 1); setTodos(todos.slice(0)); } catch (error) { console.error(error); }}; return ( <SafeAreaView> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} /> <ScrollView contentInsetAdjustmentBehavior="automatic"> <View style={[styles.appTitleView]}> <Text style={styles.appTitleText}> ToDo Application </Text> </View> <View> {todos.map((todo) => ( <ToDoItemComponent key={todo.id} todo={todo} deleteItem={deleteItem} /> ))} </View> <View style={styles.textInputContainer}> <TextInput style={styles.textInput} value={newTodo} onChangeText={text => setNewTodo(text)} /> <Button onPress={addTodo} title="Add  ToDo" color="#841584" accessibilityLabel="add todo item" /> </View> </ScrollView> </SafeAreaView> ); }; const styles = StyleSheet.create({ appTitleView: { marginTop: 20, justifyContent: 'center', flexDirection: 'row', }, appTitleText: { fontSize: 24, fontWeight: '800' }, textInputContainer: { marginTop: 30, marginLeft: 20, marginRight: 20, borderRadius: 10, borderColor: 'black', borderWidth: 1, justifyContent: 'flex-end' }, textInput: { borderWidth: 1, borderRadius: 5, height: 30, margin: 10, backgroundColor: 'pink' }, }); export default App;Copy the code

Finally, our final todoItem.tsx file should look like the following code block.

import React from 'react';
import {
  Button,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { ToDoItem } from '../models';
export const ToDoItemComponent: React.FC<{
  todo: ToDoItem;
  deleteItem: Function;
}> = ({ todo: {id, value}, deleteItem }) => {
  return (
    <View style={styles.todoContainer}>
      <View style={styles.todoTextContainer}>
        <Text
          style={styles.sectionTitle}>
          {value}
        </Text>
      </View>
      <Button
        onPress={() => deleteItem(id)}
        title="done"
        color="#841584"
        accessibilityLabel="add todo item"
      />
    </View>
  );
};
const styles = StyleSheet.create({
  todoContainer: {
    marginTop: 10,
    paddingHorizontal: 24,
    backgroundColor: 'deepskyblue',
    marginLeft: 20,
    marginRight: 20,
    borderRadius: 10,
    borderColor: 'black',
    borderWidth: 1,
  },
  todoTextContainer: {
    justifyContent: 'center',
    flexDirection: 'row',
  },
  sectionTitle: {
    fontSize: 20,
    fontWeight: '400',
  }
});

Copy the code

This is your result! Our finished React Native to-do app should look like the image below.

conclusion

In this tutorial, we learned how to connect an SQLite database to a React Native application, and then created our own application using TypeScript.

We used react-native SQlite-storage, a connector library, and completed CRUD operations. We then combined these operations with our React state update and considered the difference between simple state management and persistent data.

I hope you enjoyed this article and don’t forget to comment on any ideas and improvements. Happy coding!

The postUsing SQLite with React Native appeared first onLogRocket Blog.