Saving Tasks to Local Storage
In this tutorial, we’ll implement functionality to save tasks to the device’s local storage, allowing them to persist between app sessions. We’ll use React Native’s AsyncStorage to achieve this.
Step 1: Import AsyncStorage and Define a Storage Key
Section titled “Step 1: Import AsyncStorage and Define a Storage Key”In your main file (app/index.tsx):
import * as React from 'react';import { ScrollView, View } from 'react-native';import { Text } from '@/components/ui/text';import { Task } from '@/components/Task';import { AddTask } from '@/components/AddTask';import AsyncStorage from "@react-native-async-storage/async-storage";
// Key for storing tasks in AsyncStorageconst TASKS_STORAGE_KEY = "hallpass_tasks";
interface ITask { id: number; title: string; category: string; isChecked: boolean;}Step 2: Load Tasks from Storage on App Start
Section titled “Step 2: Load Tasks from Storage on App Start”export default function HomeScreen() { const initialTasks: ITask[] = [ { id: 1, title: 'Task 1', category: 'Category 1', isChecked: false, }, { id: 2, title: 'Task 2', category: 'Category 2', isChecked: true, }, { id: 3, title: 'Task 3', category: 'Category 3', isChecked: false, }, ]; const [tasks, setTasks] = React.useState<ITask[]>(initialTasks); const [tasks, setTasks] = React.useState<ITask[]>([]); const [isLoading, setIsLoading] = React.useState(true);
// Load tasks from storage when app starts React.useEffect(() => { const loadTasks = async () => { try { const storedTasks = await AsyncStorage.getItem(TASKS_STORAGE_KEY); if (storedTasks !== null) { setTasks(JSON.parse(storedTasks)); } } catch (error) { console.error("Failed to load tasks:", error); } finally { setIsLoading(false); } };
loadTasks(); }, []);
const handleAddTask = (title: string, category: string) => { const nextId = tasks.length > 0 ? Math.max(...tasks.map((t) => t.id)) + 1 : 1; setTasks([...tasks, { id: nextId, title, category, isChecked: false }]); };
// More code here...Step 3: Create a Function to Save Tasks
Section titled “Step 3: Create a Function to Save Tasks”Add this code to your main app/index.tsx file, inside the HomeScreen component.
It will be used shortly to load tasks from storage when the app starts:
export default function HomeScreen() { const [tasks, setTasks] = React.useState<ITask[]>([]); const [isLoading, setIsLoading] = React.useState(true);
// Load tasks from storage when app starts React.useEffect(() => {14 collapsed lines
const loadTasks = async () => { try { const storedTasks = await AsyncStorage.getItem(TASKS_STORAGE_KEY); if (storedTasks !== null) { setTasks(JSON.parse(storedTasks)); } } catch (error) { console.error('Failed to load tasks:', error); } finally { setIsLoading(false); } };
loadTasks(); }, []);
// Save tasks to storage whenever they change const saveTasks = async (updatedTasks: ITask[]) => { try { await AsyncStorage.setItem(TASKS_STORAGE_KEY, JSON.stringify(updatedTasks)); } catch (error) { console.error('Failed to save tasks:', error); } };
const handleAddTask = (title: string, category: string) => { const nextId = tasks.length > 0 ? Math.max(...tasks.map((t) => t.id)) + 1 : 1; setTasks([...tasks, { id: nextId, title, category, isChecked: false }]); };Step 4: Update Task Handlers to Save Changes
Section titled “Step 4: Update Task Handlers to Save Changes”const handleAddTask = (title: string, category: string) => { const nextId = tasks.length > 0 ? Math.max(...tasks.map((t) => t.id)) + 1 : 1; setTasks([...tasks, { id: nextId, title, category, isChecked: false }]); const updatedTasks = [ ...tasks, { id: nextId, title, category, isChecked: false }, ]; setTasks(updatedTasks); saveTasks(updatedTasks); };
const handleTaskUpdate = (updatedTask: ITask) => { const updatedTasks = tasks.map((task) => task.id === updatedTask.id ? updatedTask : task ); setTasks(updatedTasks); saveTasks(updatedTasks);};Step 5: Update the UI to Handle Loading State
Section titled “Step 5: Update the UI to Handle Loading State”Step 6: Update the Task Component to Propagate Changes
Section titled “Step 6: Update the Task Component to Propagate Changes”export interface TaskProps { task: Task; onUpdate?: (task: ITask) => void;}
function Task({ task: initialTask, onUpdate }: TaskProps) { const [task, setTask] = React.useState(initialTask); const [showDialog, setShowDialog] = React.useState(false); const { title, category, isChecked } = task;
const handleSetChecked = () => { const nextChecked = !task.isChecked; setTask({ ...task, isChecked: nextChecked }); const updatedTask = { ...task, isChecked: !task.isChecked }; setTask(updatedTask); if (onUpdate) { onUpdate(updatedTask); } };Step 7: Improve the TaskDialog Component
Section titled “Step 7: Improve the TaskDialog Component”Open components/TaskDialogue.tsx and update the following code on your Input and DialogClose JSX components:
<View className="gap-4"> <Input defaultValue={task.title} value={editedTitle} placeholder="Task title" onChangeText={handleUpdateTitle} /> <Input defaultValue={task.category} value={editedCategory} placeholder="Category" onChangeText={handleUpdateCategory} /></View>
<DialogFooter> <DialogClose asChild> <Button variant="outline"> <Text>Cancel</Text> </Button> </DialogClose> <DialogClose asChild> <Button onPress={handleSave}> <Text>Save changes</Text> </Button> </DialogClose> <Button className="border-brand-primary flex-1 rounded-3xl border bg-transparent" onPress={() => setShowDialog(false)}> <Text className="text-brand-primary">Cancel</Text> </Button> <Button className="bg-brand-primary flex-1w-1/2 rounded-3xl" onPress={handleSave}> <Text>Save changes</Text> </Button></DialogFooter>Step 8: Reset Form Fields in AddTask
Section titled “Step 8: Reset Form Fields in AddTask”How it all works together
Section titled “How it all works together”- When the app starts, it loads tasks from
AsyncStorage - While loading, it shows a “Loading tasks…” message
- If no tasks exist, it shows a prompt to add the first task
- When a task is added or updated:
- The component state is updated
- The change is propagated to the parent component
- The parent saves all tasks to
AsyncStorage - When the app is reopened later, the saved tasks are loaded from storage
Testing Your Implementation
Section titled “Testing Your Implementation”After implementing these changes:
- Add a few tasks to your app
- Close the app completely
- Reopen the app - your tasks should still be there!
- Edit a task or mark it as complete - these changes should persist after closing and reopening the app
That’s it!
Section titled “That’s it!”Congratulations! Your app now has a complete task management system with persistent storage.
Once you have implemented AsyncStorage successfully, your app now:
- Loads tasks from storage when it starts
- Saves tasks whenever they’re added or updated
- Shows a loading state while tasks are being fetched
- Displays a message when no tasks exist
- All changes are automatically saved to the user’s device!
Users can add, edit, and complete tasks, and all changes are automatically saved to their device.