'Firebase not returning ID in useCollectionData

I'm trying to make a simple firebase to-do app. I can write todos, no problem. However, when I try to delete a todo or mark one as complete, it seems the id is undefined, and firebase can't find the to-do in question. How can I correct this? For context, I am following this tutorial: https://www.youtube.com/watch?v=cuvP4h6O2x8&t=755s

Code:

import "../App.css";
import {useState} from "react";
import {auth, firestore} from "../firebase";
import firebase from "../firebase";
import {useCollectionData} from "react-firebase-hooks/firestore";

const Todos = () => {
  const [todo, setTodo] = useState("");
  const todosRef = firestore.collection(`users/${auth.currentUser.uid}/todos`);
  const [todos] = useCollectionData(todosRef, { idField: "id" });
  const signOut = () => auth.signOut();

  const onSubmitTodo = (event) => {
    event.preventDefault();
    setTodo("");
    todosRef.add({
      text: todo,
      complete: false,
      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
    });
  };

  return (
    <>
      <header>
        <button onClick = {signOut}>Sign Out</button>
      </header>
      <main>
        <form onSubmit = {onSubmitTodo}>
          <input
            required
            value = {todo}
            onChange = {(e) => setTodo(e.target.value)}
            placeholder="what's next?"
            />
            <button type="submit"> Add </button>
        </form>
        {todos && todos.map((todo)=> <Todo key={todo.id} {...todo} />)}
      </main>
    </>
  );
};


const Todo = ({ id, complete, text}) => {
  const todosRef = firestore.collection(`users/${auth.currentUser.uid}/todos`);
  const onCompleteTodo = (id, complete) =>
    todosRef.doc(id).set({ complete: !complete }, { merge: true });

  const onDeleteTodo = (id) => todosRef.doc(id).delete();

  return (
    <div key={id} className="todo">
      <button
        className={`todo-item ${complete ? "complete" : ""}`}
        tabIndex="0"
        onClick={()=> onCompleteTodo(id, complete)}
        >
          {text}
        </button>
        <button onClick={() => onDeleteTodo(id)}>x</button>
    </div>
  );
};

export default Todos;


Solution 1:[1]

React Firebase Hooks v5 matches Firebase 9 and above, in which you should use the FirestoreDataConverter interface.

See the documentation.

The examples use TypeScript, but in your case something like this should work, applying withConverter and your custom converter right after getting the collection:

const converter = {
    toFirestore(post) {
        return {
            text: post.text,
        }
    },
    fromFirestore(snapshot, options) {
        const data = snapshot.data(options)
        return {
            id: snapshot.id,
            text: data.text,
        }
    },
}

const todosRef = firestore
    .collection(`users/${auth.currentUser.uid}/todos`)
    .withConverter(converter);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 alotropico