Data Persistence and Fetching
Mobile apps need two different kinds of data handling: fetching from a remote API (same fetch/useEffect pattern as web React), and persisting small bits of data locally so they survive an app restart.
Fetching data
import { useEffect, useState } from 'react';
function useCourses() {
const [courses, setCourses] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://api.example.com/courses')
.then((res) => res.json())
.then(setCourses)
.finally(() => setLoading(false));
}, []);
return { courses, loading };
}
This is identical to fetching in web React, fetch and useEffect work exactly the same in React Native.
Persisting data with AsyncStorage
There's no browser localStorage on mobile, the equivalent is @react-native-async-storage/async-storage, an async key-value store.
import AsyncStorage from '@react-native-async-storage/async-storage';
async function saveDraft(text) {
await AsyncStorage.setItem('noteDraft', text);
}
async function loadDraft() {
const value = await AsyncStorage.getItem('noteDraft');
return value ?? '';
}
Every method is async and returns a Promise, unlike localStorage's synchronous API, this matters when you're loading saved data on app startup, you'll typically do it inside a useEffect and show a loading state until it resolves.
Storing objects
AsyncStorage only stores strings, so store objects as JSON:
async function saveTasks(tasks) {
await AsyncStorage.setItem('tasks', JSON.stringify(tasks));
}
async function loadTasks() {
const raw = await AsyncStorage.getItem('tasks');
return raw ? JSON.parse(raw) : [];
}
WARNING
AsyncStorage is meant for small amounts of data (settings, drafts, a cached list), it is not encrypted and not meant for sensitive data like auth tokens, for those, use expo-secure-store instead, which stores values in the device's encrypted keychain.