Building a React Native App with Offline Storage
Imagine having a mobile app that can store data locally, without relying on an internet connection. Sounds appealing, right? In this article, we’ll explore how to build a React Native app that allows users to track their weight and visualize their progress, all while storing data offline.
Getting Started
Before we dive into the project, make sure you have React Native set up on your machine. If you’re new to React Native, follow the official environment setup documentation to get started. Once you’ve set up your machine, create a new React Native project using the command npx react-native init weightress
.
Database Setup
For offline storage, we’ll use WatermelonDB, a powerful and easy-to-use database solution for React Native apps. Install WatermelonDB using Yarn by running yarn add @nozbe/watermelondb
. Then, create a new folder named data
and add a file called schema.ts
with the following code:
“`ts
import { schema } from ‘@nozbe/watermelondb’;
const weightsSchema = schema({
name: ‘weights’,
columns: [
{ name: ‘weight’, type: ‘number’ },
{ name: ‘note’, type: ‘tring’, isOptional: true },
{ name: ‘createdat’, type: ‘number’ },
],
indexes: [
{ name: ‘noteindex’, columns: [‘note’] },
{ name: ‘createdatindex’, columns: [‘created_at’] },
],
});
export default weightsSchema;
“
weights
This schema defines atable with three columns:
weight,
note, and
created_at`. We’ll use this schema to store user input data.
Model and Database Instance
Next, create a new file called weight.ts
in the data
folder with the following code:
“`ts
import { Model } from ‘@nozbe/watermelondb’;
import { weightsSchema } from ‘./schema’;
class Weight extends Model {
static table = ‘weights’;
@date(‘created_at’)
createdAt!: number;
weight!: number;
note?: string;
}
export default Weight;
“
Weight
This file defines amodel that maps to the
weights` table in our schema.
Create another file called database.ts
in the data
folder with the following code:
“`ts
import { Database } from ‘@nozbe/watermelondb’;
import { weightsSchema } from ‘./schema’;
import Weight from ‘./weight’;
const database = new Database({
schema: weightsSchema,
models: [Weight],
});
export default database;
“`
This file creates a new database instance using our schema and model.
Data Helpers
Create a new file called helpers.ts
in the data
folder with the following code:
“`ts
import { database } from ‘./database’;
import Weight from ‘./weight’;
const observeWeights = () => {
return database.collections.get(‘weights’).query();
};
const saveWeight = (weight: number, note?: string) => {
return database.write(() => {
const weightEntry = new Weight({
weight,
note,
});
database.collections.get(‘weights’).create(weightEntry);
});
};
export { observeWeights, saveWeight };
“
observeWeights
This file defines two helper functions:returns a query that observes the
weightstable, and
saveWeight` saves a new weight entry to the database.
User Interface
Now that we have our database set up, let’s create the user interface for our app. Create a new folder called components
and add three files: header.tsx
, creator.tsx
, and chart.tsx
.
Header Component
In header.tsx
, add the following code:
“`jsx
import React from ‘eact’;
import { View, Text, TouchableOpacity } from ‘eact-native’;
const Header = () => {
return (
);
};
const styles = {
container: {
flex: 1,
justifyContent: ‘pace-between’,
alignItems: ‘center’,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: ‘bold’,
},
addButton: {
fontSize: 24,
color: ‘#007AFF’,
},
};
export default Header;
“`
This component displays a header with a title and an add button.
Creator Component
In creator.tsx
, add the following code:
“`jsx
import React, { useState } from ‘eact’;
import { View, Text, TextInput, TouchableOpacity } from ‘eact-native’;
const Creator = () => {
const [weight, setWeight] = useState(”);
const [note, setNote] = useState(”);
const [isSaving, setIsSaving] = useState(false);
const handleSavePress = async () => {
setIsSaving(true);
try {
await saveWeight(weight, note);
setIsSaving(false);
} catch (error) {
console.error(error);
}
};
return (
);
};
const styles = {
container: {
flex: 1,
padding: 16,
},
header: {
fontSize: 24,
fontWeight: ‘bold’,
},
input: {
height: 40,
borderColor: ‘#ccc’,
borderWidth: 1,
padding: 10,
marginBottom: 16,
},
saveButton: {
fontSize: 24,
color: ‘#007AFF’,
},
};
export default Creator;
“`
This component displays a form with two input fields and a save button.
Chart Component
In chart.tsx
, add the following code:
“`jsx
import React from ‘eact’;
import { View, Text } from ‘eact-native’;
import { LineChart } from ‘eact-native-chart-kit’;
const Chart = ({ weights }) => {
const data = weights.map((weight) => weight.weight);
const labels = weights.map((weight) => weight.createdAt);
return (
}}
/>
);
};
const styles = {
container: {
flex: 1,
padding: 16,
},
};
export default Chart;
“
react-native-chart-kit` library.
This component displays a line chart using the
Putting it all Together
Finally, open up App.tsx
and add the following code:
“`jsx
import React from ‘eact’;
import { View, Text, ScrollView } from ‘eact-native’;
import Header from ‘./components/Header’;
import Creator from ‘./components/Creator’;
import Chart from ‘./components/Chart’;
import { observeWeights } from ‘./data/helpers’;
const App = () => {
const [showCreator, setShowCreator] = useState(false);
const weights = observeWeights();
return (
{showCreator &&
);
};
const styles = {
container: {
flex: 1,
padding: 16,
},
};
export default App;
“
weights
This component renders the header, creator, and chart components, and observes thetable using the
observeWeights` helper function.
That’s it! You now have a React Native app that allows users to track their weight and visualize their progress, all while storing data offline using WatermelonDB.