kita lanjutkan project sebelumnya dimana pada tutorial sebelumnya kita sudah berhasil membuat login, signup dan authentikasi ke firestore database. Pada tutorial ini kita akan lanjutkan dengan menambahkan fitur CRUD.

A. Buat struktur komponen

buat folder baru dengan nama components pada folder (screens) yang berisi file EditTodoModal.jsx, Todoinput.jsx, Todoitem.jsx dan TodoList.jsx .

berikut struktur lengkapnya :

Image description

B. 📄 components/Todoinput.jsx

import React from "react";
import { TextInput, Button, View } from "react-native";

export default function TodoInput({ todo, setTodo, addTodo }) {
  return (
    
      
      
    
  );
}

Image description

C. 📄 components/Todoitem.jsx

import React from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";

export default function TodoItem({ item, onToggle, onDelete, onEdit }) {
  return (
    
       onToggle(item.id, item.completed)} style={styles.textContainer}>
        {item.title}
      
      
         onEdit(item)}>
          ✏️
        
         onDelete(item.id)}>
          🗑️
        
      
    
  );
}

const styles = StyleSheet.create({
  item: {
    paddingVertical: 12,
    paddingHorizontal: 16,
    backgroundColor: "#f1f1f1",
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    borderBottomWidth: 1,
    borderBottomColor: "#ccc", // Garis pembatas
  },
  textContainer: {
    flex: 1,
  },
  text: {
    fontSize: 16,
  },
  completed: {
    textDecorationLine: "line-through",
    color: "gray",
  },
  actions: {
    flexDirection: "row",
    marginLeft: 10,
  },
  actionText: {
    fontSize: 18,
    marginLeft: 10,
  },
});

Komponen TodoItem ini berfungsi untuk menampilkan satu item todo (tugas)
Image description

D. 📄 components/TodoList.jsx

import React from "react";
import { FlatList } from "react-native";
import TodoItem from "./Todoitem";

export default function TodoList({ todos, onToggle, onDelete, onEdit }) {
  return  item.id} renderItem={({ item }) => } />;
}

Komponen TodoList ini digunakan untuk menampilkan daftar to-do secara otomatis dan efisien dalam bentuk list menggunakan FlatList dari React Native.
Image description

E. 📄 components/EditTodoModal.jsx

import React from "react";
import { Modal, View, Text, TextInput, Button } from "react-native";

export default function EditTodoModal({ visible, editText, setEditText, onSave, onCancel }) {
  return (
    
      
        
          Edit To-Do
          
          
          
          
        
      
    
  );
}

Komponen EditTodoModal digunakan untuk menampilkan pop-up (modal) saat pengguna ingin mengedit to-do.

Image description

F. Update di Home.jsx

import React, { useEffect, useState } from "react";
import { View, Text, TextInput, Button, FlatList, Modal, Alert, ActivityIndicator } from "react-native";
import { useRouter } from "expo-router";
import { signOut, onAuthStateChanged } from "firebase/auth";
import { auth, db } from "../../config/firebase";
import { collection, addDoc, query, where, onSnapshot, deleteDoc, updateDoc, doc, orderBy } from "firebase/firestore";
import TodoItem from "./components/Todoitem"; // Pastikan file Todoitem.jsx ada di folder components

export default function Home() {
  const [user, setUser] = useState(null);
  const [loadingUser, setLoadingUser] = useState(true);
  const [todo, setTodo] = useState("");
  const [todos, setTodos] = useState([]);
  const [editModalVisible, setEditModalVisible] = useState(false);
  const [editTodo, setEditTodo] = useState(null);
  const [editText, setEditText] = useState("");
  const router = useRouter();

  // Cek user login
  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
      if (!currentUser) {
        router.replace("/Login");
      } else {
        setUser(currentUser);
      }
      setLoadingUser(false);
    });

    return unsubscribe;
  }, []);

  // Ambil data todo dari Firestore
  useEffect(() => {
    if (!user) return;

    const q = query(collection(db, "todos"), where("userId", "==", user.uid), orderBy("createdAt", "desc"));

    const unsubscribe = onSnapshot(q, (snapshot) => {
      const list = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
      setTodos(list);
    });

    return unsubscribe;
  }, [user]);

  const addTodo = async () => {
    if (todo.trim() === "") return;

    await addDoc(collection(db, "todos"), {
      title: todo,
      completed: false,
      createdAt: new Date(),
      userId: user.uid,
    });

    setTodo("");
  };

  const toggleComplete = async (id, status) => {
    await updateDoc(doc(db, "todos", id), { completed: !status });
  };

  const deleteTodo = async (id) => {
    await deleteDoc(doc(db, "todos", id));
  };

  const openEditModal = (item) => {
    setEditTodo(item);
    setEditText(item.title);
    setEditModalVisible(true);
  };

  const handleEditSave = async () => {
    if (!editText.trim()) {
      Alert.alert("Isi tidak boleh kosong");
      return;
    }

    await updateDoc(doc(db, "todos", editTodo.id), {
      title: editText,
    });

    setEditModalVisible(false);
    setEditTodo(null);
    setEditText("");
  };

  const handleLogout = async () => {
    await signOut(auth);

    //tambahkan untuk membersihkan textinput email dan password
    router.replace({ pathname: "/Login", params: { reset: "true" } });
  };

  // Tampilkan loading selama pengecekan user
  if (loadingUser) {
    return (
      
        
      
    );
  }

  return (
    
      To-Do List
      Welcome, {user?.email}
      

      

       item.id}
        renderItem={({ item }) => }
        ListEmptyComponent={Belum ada todo}
      />

      
        
      

      {/* Modal Edit */}
       setEditModalVisible(false)}>
        
          
            Edit To-Do
            
            
            
             setEditModalVisible(false)} />
          
        
      
    
  );
}

Komponen ini adalah halaman utama aplikasi To-Do List. Di sini pengguna bisa:

  • Melihat daftar to-do miliknya
  • Menambahkan to-do baru
  • Mengedit to-do
  • Menandai to-do sebagai selesai
  • Menghapus to-do
  • Logout dari akun

Image description

Image description

Image description

Berikut adalah tampilan aplikasi :

Image description

Image description

Image description

ERROR YANG UMUMNYA TERJADI

tambah data sudah bisa namun ketika browser di refresh data tersebut hilang pada aplikasi , dan data juga tidak bisa di edit serta di hapus secara real time .
ada pesan error seperti ini pada browser:

C:\Users\User\Documents\reactnative\ReactFirebaseApp\node_modules\@expo\metro-runtime\src\error-overlay\LogBox.web.ts:134  [2025-04-08T07:31:21.400Z]  @firebase/firestore: Firestore (11.4.0): Uncaught Error in snapshot listener: FirebaseError: [code=failed-precondition]: The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/react-native-project-1-cd8e8/firestore/indexes?create_composite=Clpwcm9qZWN0cy9yZWFjdC1uYXRpdmUtcHJvamVjdC0xLWNkOGU4L2RhdGFiYXNlcy8oZGVmYXVsdCkvY29sbGVjdGlvbkdyb3Vwcy90b2Rvcy9pbmRleGVzL18QARoKCgZ1c2VySWQQARoNCgljcmVhdGVkQXQQAhoMCghfX25hbWVfXxAC

SOLUSI :
error yang dialami muncul karena Firestore memerlukan composite index untuk query yang kamu buat dengan kombinasi where("userId", "==", ...) dan orderBy("createdAt", "desc").
🔧 Cara Mengatasinya
Klik link di error:
sebagai contoh link : https://console.firebase.google.com/v1/r/project/react-native-project-1-cd8e8/firestore/indexes?create_composite=Clpwcm9qZWN0cy9yZWFjdC1uYXRpdmUtcHJvamVjdC0xLWNkOGU4L2RhdGFiYXNlcy8oZGVmYXVsdCkvY29sbGVjdGlvbkdyb3Vwcy90b2Rvcy9pbmRleGVzL18QARoKCgZ1c2VySWQQARoNCgljcmVhdGVkQXQQAhoMCghfX25hbWVfXxAC

Setelah terbuka, langsung klik “Create Index” / “Buat Indeks”. Tunggu beberapa saat hingga index selesai dibuat (1–2 menit biasanya).

Mengapa Data Hilang Saat Refresh?
Sebenarnya datanya tidak hilang, hanya saja query-nya gagal dieksekusi karena belum ada index, jadi onSnapshot() tidak berhasil mengambil data.

Tentang Edit & Hapus yang Tidak Bekerja Real-Time
Setelah index berhasil dibuat, semua fitur seperti:

  • Real-time onSnapshot()
  • Edit dengan updateDoc()
  • Delete dengan deleteDoc() …akan berfungsi seperti yang diharapkan.