Skip to content

Mastering Data Persistence in React Native and Expo

Posted on:April 15, 2025

Welcome back, Developer! 👋

In our last post — From Zero to App: Your First React Native App with Expo — we built a simple but functional app where you could manage users, add profile pictures, and handle some basic data.

Now it’s time to take things up a notch 😎

This time, we’re diving into data persistence — making sure your app remembers things like user preferences, login sessions, and settings even after it’s been closed.

We’ll explore some of the main storage options available in the React Native and Expo ecosystem:

  1. AsyncStorage — the go-to for simple key-value storage.
  2. Expo SecureStore — perfect for securely storing sensitive data like tokens.

You’ll not only know when to use each one, but you’ll also see them in action — all within a real-world student-focused app. Sold?


Table of contents

Open Table of contents

Intro

We’re continuing with our User Management app but giving it a more specific purpose — a simple mobile companion for students to manage their daily life on campus. Think of it as a place where students can log in, update their profile, switch between dark and light themes, and (eventually) keep track of things like tasks and study sessions.

In this post, we’ll enhance the app by making sure it remembers your login, preferences, and other settings — even after you close and reopen it.

Grab your favorite beverage and let’s dive in!


Setting things up

Before we get into the implementation, we’ll need to set up the necessary dependencies. Open your terminal and install the required packages:

npx create-expo-app@latest student-mate --template default
 
cd student-mate
 
npx expo install @react-native-async-storage/async-storage expo-secure-store

Boom 💥 you’re ready.


Time to build

In the next sections, we are using different scenarios where we store and read data to illustrate the implementation of the storage solutions.

TL;DR

AsyncStorage

Let’s persist the user’s login using AsyncStorage. On app/(tabs)/index.tsx, replace the existing code with the following:

import React, { useEffect, useState } from "react";
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  useColorScheme,
  Alert,
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
 
export default function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const colorScheme = useColorScheme();
 
  const isDark = colorScheme === "dark";
  const theme = isDark ? darkTheme : lightTheme;
 
  useEffect(() => {
    const checkLoginStatus = async () => {
      const user = await AsyncStorage.getItem("user");
      if (user) {
        setIsLoggedIn(true);
        setUsername(user);
      }
    };
    checkLoginStatus();
  }, []);
 
  const handleLogin = async () => {
    if (username && password) {
      await AsyncStorage.setItem("user", username);
 
      setIsLoggedIn(true);
    } else {
      Alert.alert("Username and Password required!");
    }
  };
 
  const handleLogout = async () => {
    await AsyncStorage.removeItem("user");
 
    setIsLoggedIn(false);
    setUsername("");
    setPassword("");
  };
 
  return (
    <View style={[styles.container, { backgroundColor: theme.background }]}>
      <Text style={[styles.title, { color: theme.text }]}>Student Mate</Text>
      {isLoggedIn ? (
        <View style={styles.innerContainer}>
          <Text style={[styles.welcomeText, { color: theme.text }]}>
            Welcome, {username}!
          </Text>
          <TouchableOpacity
            style={[styles.button, { backgroundColor: theme.primary }]}
            onPress={handleLogout}
          >
            <Text style={styles.buttonText}>Logout</Text>
          </TouchableOpacity>
        </View>
      ) : (
        <View style={styles.innerContainer}>
          <TextInput
            style={[
              styles.input,
              {
                backgroundColor: theme.inputBackground,
                color: theme.text,
                borderColor: theme.border,
              },
            ]}
            placeholder="Username"
            placeholderTextColor={theme.placeholder}
            value={username}
            onChangeText={setUsername}
          />
          <TextInput
            style={[
              styles.input,
              {
                backgroundColor: theme.inputBackground,
                color: theme.text,
                borderColor: theme.border,
              },
            ]}
            placeholder="Password"
            placeholderTextColor={theme.placeholder}
            value={password}
            onChangeText={setPassword}
            secureTextEntry
          />
          <TouchableOpacity
            style={[styles.button, { backgroundColor: theme.primary }]}
            onPress={handleLogin}
          >
            <Text style={styles.buttonText}>Login</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
}
 
const lightTheme = {
  background: "#FFFFFF",
  text: "#222222",
  primary: "purple",
  inputBackground: "#F2F2F7",
  border: "#D1D1D6",
  placeholder: "#999999",
};
 
const darkTheme = {
  background: "#121212",
  text: "#FFFFFF",
  primary: "purple",
  inputBackground: "#1E1E1E",
  border: "#3A3A3C",
  placeholder: "#AAAAAA",
};
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 24,
  },
  title: {
    fontSize: 28,
    fontWeight: "bold",
    marginBottom: 32,
  },
  innerContainer: {
    alignItems: "center",
    width: "100%",
  },
  welcomeText: {
    fontSize: 22,
    fontWeight: "600",
    marginBottom: 20,
  },
  input: {
    width: "100%",
    padding: 14,
    borderWidth: 1,
    borderRadius: 8,
    marginBottom: 16,
    fontSize: 16,
  },
  button: {
    width: "100%",
    padding: 14,
    borderRadius: 8,
    alignItems: "center",
  },
  buttonText: {
    color: "#FFFFFF",
    fontWeight: "600",
    fontSize: 16,
  },
});

How it works

Let’s walk through the core logic of the login feature, Developer. The focus is on how AsyncStorage helps us persist user sessions across app restarts.

  1. Check if user is already logged in when the app loads, we use the useEffect hook to check if a user has already logged in before:
useEffect(() => {
  const checkLoginStatus = async () => {
    const user = await AsyncStorage.getItem("user");
    if (user) {
      setIsLoggedIn(true);
      setUsername(user);
    }
  };
  checkLoginStatus();
}, []);

Here’s what’s happening:

  1. Login and save credentials when the user submits the login form, we verify the credentials and save the username:
const handleLogin = async () => {
  if (username && password) {
    await AsyncStorage.setItem("user", username);
 
    // update local state
    setIsLoggedIn(true);
  } else {
    Alert.alert("Username and Password are required!");
  }
};

Here’s what’s happening:

  1. Logout and clear session. Logging out clears the session from both memory and storage:
const handleLogout = async () => {
  // remove user data from async storage
  await AsyncStorage.removeItem("user");
 
  // update local state
  setIsLoggedIn(false);
  setUsername("");
  setPassword("");
};

Here’s what’s happening:

So why use AsyncStorage? AsyncStorage is React Native’s simple key-value storage system. It’s perfect for:

AsyncStorage isn’t encrypted — don’t store tokens or anything private.


Expo SecureStore

Now let’s see how we can securely store user data in our Student Mate app. Here we use Expo SecureStore to save the password (just for demonstration purposes — don’t do this in production!)

Let’s adjust the previous code by adding the logic to store the user password:

import React, { useEffect, useState } from "react";
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  useColorScheme,
  Alert,
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from "expo-secure-store";
 
export default function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const colorScheme = useColorScheme();
 
  const isDark = colorScheme === "dark";
  const theme = isDark ? darkTheme : lightTheme;
 
  useEffect(() => {
    const checkLoginStatus = async () => {
      const user = await AsyncStorage.getItem("user");
      if (user) {
        const savedPassword = await SecureStore.getItemAsync("password");
 
        if (savedPassword) {
          setUsername(user);
          setPassword(savedPassword);
          setIsLoggedIn(true);
        }
      }
    };
    checkLoginStatus();
  }, []);
 
  const handleLogin = async () => {
    if (username && password) {
      await AsyncStorage.setItem("user", username);
 
      await SecureStore.setItemAsync("password", password);
 
      setIsLoggedIn(true);
    } else {
      Alert.alert("Username and Password required!");
    }
  };
 
  const handleLogout = async () => {
    await AsyncStorage.removeItem("user");
 
    await SecureStore.deleteItemAsync("password");
 
    setIsLoggedIn(false);
    setUsername("");
    setPassword("");
  };
 
  return (
    <View style={[styles.container, { backgroundColor: theme.background }]}>
      <Text style={[styles.title, { color: theme.text }]}>Student Mate</Text>
      {isLoggedIn ? (
        <View style={styles.innerContainer}>
          <Text style={[styles.welcomeText, { color: theme.text }]}>
            Welcome, {username}!
          </Text>
          <TouchableOpacity
            style={[styles.button, { backgroundColor: theme.primary }]}
            onPress={handleLogout}
          >
            <Text style={styles.buttonText}>Logout</Text>
          </TouchableOpacity>
        </View>
      ) : (
        <View style={styles.innerContainer}>
          <TextInput
            style={[
              styles.input,
              {
                backgroundColor: theme.inputBackground,
                color: theme.text,
                borderColor: theme.border,
              },
            ]}
            placeholder="Username"
            placeholderTextColor={theme.placeholder}
            value={username}
            onChangeText={setUsername}
          />
          <TextInput
            style={[
              styles.input,
              {
                backgroundColor: theme.inputBackground,
                color: theme.text,
                borderColor: theme.border,
              },
            ]}
            placeholder="Password"
            placeholderTextColor={theme.placeholder}
            value={password}
            onChangeText={setPassword}
            secureTextEntry
          />
          <TouchableOpacity
            style={[styles.button, { backgroundColor: theme.primary }]}
            onPress={handleLogin}
          >
            <Text style={styles.buttonText}>Login</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
}
 
const lightTheme = {
  background: "#FFFFFF",
  text: "#222222",
  primary: "purple",
  inputBackground: "#F2F2F7",
  border: "#D1D1D6",
  placeholder: "#999999",
};
 
const darkTheme = {
  background: "#121212",
  text: "#FFFFFF",
  primary: "purple",
  inputBackground: "#1E1E1E",
  border: "#3A3A3C",
  placeholder: "#AAAAAA",
};
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 24,
  },
  title: {
    fontSize: 28,
    fontWeight: "bold",
    marginBottom: 32,
  },
  innerContainer: {
    alignItems: "center",
    width: "100%",
  },
  welcomeText: {
    fontSize: 22,
    fontWeight: "600",
    marginBottom: 20,
  },
  input: {
    width: "100%",
    padding: 14,
    borderWidth: 1,
    borderRadius: 8,
    marginBottom: 16,
    fontSize: 16,
  },
  button: {
    width: "100%",
    padding: 14,
    borderRadius: 8,
    alignItems: "center",
  },
  buttonText: {
    color: "#FFFFFF",
    fontWeight: "600",
    fontSize: 16,
  },
});

Behind the scenes

  1. Auto-login with stored credentials
useEffect(() => {
  const checkLoginStatus = async () => {
    const user = await AsyncStorage.getItem("user");
    if (user) {
      const savedPassword = await SecureStore.getItemAsync("password");
      if (savedPassword) {
        setUsername(user);
        setPassword(savedPassword);
        setIsLoggedIn(true);
      }
    }
  };
  checkLoginStatus();
}, []);

Here’s what’s happening:

Passwords should never be stored in plain text — SecureStore keeps them encrypted using native device-level protection (Keychain on iOS, Keystore on Android).

  1. Storing Credentials on Login
const handleLogin = async () => {
  if (username && password) {
    await AsyncStorage.setItem("user", username);
    await SecureStore.setItemAsync("password", password);
    setIsLoggedIn(true);
  } else {
    Alert.alert("Username and Password required!");
  }
};

Here’s what’s happening:


  1. Clearing Storage on Logout
const handleLogout = async () => {
  await AsyncStorage.removeItem("user");
  await SecureStore.deleteItemAsync("password");
 
  setIsLoggedIn(false);
  setUsername("");
  setPassword("");
};

Here’s what’s happening:

So why use Expo SecureStore? Expo SecureStore is a simple, secure storage solution for sensitive data in mobile apps. It’s ideal for:

SecureStore isn’t ideal for large data or frequent writes/reads.


You Did It — Congrats

Congrats! You’ve just learned how to persist data in your Expo app using AsyncStorage and SecureStore. That means your app can now remember things — even after it’s closed. Pretty cool, right?

These are the kinds of patterns that turn good apps into great ones. And more importantly, you’ve built up your confidence to make smart architectural decisions when it comes to data storage on mobile devices.

But this is just the beginning. Keep experimenting, keep refactoring, and keep building!

Thanks for reading, and I can’t wait to see what you build next. If you found this helpful, stick around and stay tuned for more tutorials.

See you in the next one, Developer. Enjoy the journey and have fun! 💙