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! ๐Ÿš€

Top comments (0)