Thuta Learning
ProjectsMobile Developmentintermediate

Mini Project Part 2: Todo App — State & Persistence

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Get a no-fear understanding of Mini Project Part 2: Todo App — State & Persistence
  • Write the code yourself and run it on Expo Go
  • Apply this concept immediately in a real app project

Let's think about it this way for a moment

We'll turn the todo array into state with useState([]) and write three functions — addTodo, toggleTodo, and deleteTodo. addTodo appends immutably with the spread operator ([...todos, newTodo]), toggleTodo uses .map() to flip just the done field on the target item, and deleteTodo uses .filter() to remove the target id. With a useEffect that auto-saves to AsyncStorage every time the todo array changes, your data will still be there each time you close and reopen the app.

Let's connect it to a real scenario

On app mount, first load the todo list from AsyncStorage with useEffect(() => { loadTodos() }, []). Then use a separate useEffect (deps: [todos]) to auto-save with AsyncStorage.setItem every time the todo array changes. Watch out here — the save effect might end up re-writing right after the initial load and create an infinite loop, so it's best to keep them clearly separated with a loading state flag.

Code Example

javascript
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

function useTodos() {
  const [todos, setTodos] = useState([]);
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    AsyncStorage.getItem('todos').then((json) => {
      if (json) setTodos(JSON.parse(json));
      setLoaded(true);
    });
  }, []);

  useEffect(() => {
    if (loaded) AsyncStorage.setItem('todos', JSON.stringify(todos));
  }, [todos, loaded]);

  const addTodo = (text) =>
    setTodos([...todos, { id: Date.now().toString(), text, done: false }]);
  const toggleTodo = (id) =>
    setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
  const deleteTodo = (id) => setTodos(todos.filter((t) => t.id !== id));

  return { todos, addTodo, toggleTodo, deleteTodo };
}
You should see
After adding, ticking, or deleting a todo item, the data will still be there when you close and reopen the app.

5-Minute Try-It

Wire the useTodos hook into Part 1's UI and implement Add button press → addTodo, checkbox press → toggleTodo.

A Quick Word of Caution

If you mutate state directly (todos.push(newTodo)), React can't detect the change and won't re-render — always build a new array/object first, then call setTodos.

Easy traps

  • Running the save useEffect without a loading flag, which can overwrite AsyncStorage with an empty array (before the initial load even finishes)
  • Directly mutating the array with .push()/.splice() (stick to the immutable pattern — .map()/.filter()/spread only)

Now Try It Yourself

Wire the useTodos hook into Part 1's UI and implement Add button press → addTodo, checkbox press → toggleTodo.

You'll know it worked when: After adding, ticking, or deleting a todo item, the data will still be there when you close and reopen the app.

Mini Project Part 2: Todo App — State & Persistence | Thuta Learning