DEV Community

Cover image for Building a To-Do App in React Native with Theming options and AsyncStorage
Arun Kumar
Arun Kumar

Posted on

Building a To-Do App in React Native with Theming options and AsyncStorage

Building a To-Do App in React Native with Theming options and AsyncStorage

Introduction

In this guide, we will walk through creating a To-Do App in React Native. This app will feature:

  • Adding, deleting, and marking tasks as completed
  • Dark mode support using a custom switch
  • Persistent storage with AsyncStorage
  • Smooth UI with custom styling and animations

Let's get started! πŸš€


Prerequisites

Before we begin, ensure you have the following installed:

  • Node.js
  • Expo CLI
  • A code editor (e.g., VS Code)
  • An emulator or a physical device with Expo Go

Step 1: Set Up a New React Native Project

Run the following command to create a new React Native project using Expo:

npx create-expo-app todo-app
cd todo-app
npm install
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Required Dependencies

We will use AsyncStorage for local data storage and Expo Icons for UI icons.

npm install @react-native-async-storage/async-storage
npm install @expo/vector-icons
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Main App Component

Create an App.js file and paste the following code:

import React, { useState, useEffect } from 'react';
import {
  View, Text, FlatList, TouchableOpacity, TextInput, StyleSheet, Modal, Image, StatusBar, SafeAreaView
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AntDesign } from '@expo/vector-icons';
import CustomSwitch from './CustomSwitch';

const App = () => {
  const [tasks, setTasks] = useState([]);
  const [isModalVisible, setModalVisible] = useState(false);
  const [input, setInput] = useState('');
  const [darkMode, setDarkMode] = useState(false);

  const toggleTheme = () => setDarkMode(!darkMode);

  const addTask = () => {
    if (!input.trim()) return;
    const newTask = { id: Date.now(), text: input, completed: false };
    setTasks([...tasks, newTask]);
    setInput('');
    setModalVisible(false);
  };

  const deleteTask = (id) => setTasks(tasks.filter((task) => task.id !== id));

  const toggleCompleted = (id) => {
    setTasks(tasks.map(task => task.id === id ? { ...task, completed: !task.completed } : task));
  };

  useEffect(() => {
    const loadTasks = async () => {
      try {
        const storedTasks = await AsyncStorage.getItem('tasks');
        if (storedTasks) setTasks(JSON.parse(storedTasks));
      } catch (error) {
        console.error('Error loading tasks:', error);
      }
    };
    loadTasks();
  }, []);

  useEffect(() => {
    const saveTasks = async () => {
      try {
        await AsyncStorage.setItem('tasks', JSON.stringify(tasks));
      } catch (error) {
        console.error('Error saving tasks:', error);
      }
    };
    saveTasks();
  }, [tasks]);

  return (
    <SafeAreaView style={[styles.container, darkMode ? styles.darkBackground : styles.lightBackground]}>
      <StatusBar barStyle={darkMode ? "light-content" : "dark-content"} backgroundColor={darkMode ? "#b5b7f5" : "#9395d3"} />
      <View style={[styles.header, darkMode ? styles.darkBackgroundAccent : styles.lightBackgroundAccent]}>
        <Text style={[styles.headerText, darkMode ? styles.darkText : styles.lightText]}>Todo App</Text>
        <CustomSwitch value={darkMode} onValueChange={toggleTheme} />
      </View>
      <FlatList
        data={tasks}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <View style={[styles.task, darkMode ? styles.cardBackgroundDark : styles.cardBackgroundLight]}>
            <Text style={[styles.taskText, item.completed && styles.completedTask, darkMode ? styles.darkText : styles.lightText]}>{item.text}</Text>
            <View style={styles.taskActions}>
              <TouchableOpacity onPress={() => toggleCompleted(item.id)}>
                <Image style={styles.tickImage} source={require('./assets/Completed.png')} />
              </TouchableOpacity>
              <TouchableOpacity onPress={() => deleteTask(item.id)}>
                <Image style={styles.deleteImage} source={require('./assets/Delete.png')} />
              </TouchableOpacity>
            </View>
          </View>
        )}
      />
      <TouchableOpacity style={[styles.addButton, darkMode ? styles.darkBackgroundAccent : styles.lightBackgroundAccent]} onPress={() => setModalVisible(true)}>
        <AntDesign name="plus" size={24} color="white" />
      </TouchableOpacity>
    </SafeAreaView>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Custom Switch Component

Create a file called CustomSwitch.js and paste the following code:

import React, { useRef, useEffect } from 'react';
import { TouchableOpacity, View, StyleSheet, Animated } from 'react-native';
import Feather from '@expo/vector-icons/Feather';
import Ionicons from '@expo/vector-icons/Ionicons';

const CustomSwitch = ({ value, onValueChange }) => {
  const translateX = useRef(new Animated.Value(value ? 20 : 0)).current;

  useEffect(() => {
    Animated.timing(translateX, {
      toValue: value ? 20 : 0,
      duration: 200,
      useNativeDriver: true,
    }).start();
  }, [value]);

  return (
    <TouchableOpacity style={[styles.switch, value ? styles.onBackground : styles.offBackground]} onPress={onValueChange}>
      <Animated.View style={[styles.toggle, { transform: [{ translateX }] }]}> 
        {value ? <Ionicons name="moon" size={15} color="black" /> : <Feather name="sun" size={15} color="black" />}
      </Animated.View>
    </TouchableOpacity>
  );
};

export default CustomSwitch;
Enter fullscreen mode Exit fullscreen mode

Step 5: Run the App

Now, start your development server by running:

npx expo start
Enter fullscreen mode Exit fullscreen mode

Scan the QR code with Expo Go on your phone to preview the app!


Conclusion

Congratulations! πŸŽ‰ You have successfully built a To-Do App with React Native featuring dark mode, persistent storage, and smooth UI. Happy coding! πŸš€

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free β†’

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay