0

I have an async function that adds a new item to the topics collection.

It works well but the problem is that I wanted to refresh the array of topics in React with the item I just created but I'm not able to get the information of the new item in the .then() property.

I've searched and when firebase worked the following example the .then() would work:

db.collection("cities").add({
        name: "Tokyo",
        country: "Japan"
    })

My current function is this:

 const handleSubmitTopic = async () => {
    const topicRef = collection(db, "topics");
    try {
      await setDoc(doc(topicRef), {
        userUID: user.uid,
        name: topicText,
      });

    } catch (e) {
      console.log(e);
    }
  };

If I use the

.then((e) => {
   console.log(e)
}

I get an undefined.

1
  • 2
    If you want to display up to date data in your UI, I recommend using a realtime listener rather than trying to update the UI from the then of your write operation. Realtime listeners are automatically invoked after each remote or local update, and fit much better with React's reactive flow. Commented Jun 9, 2022 at 12:06

1 Answer 1

1

When you call setDoc(docRef, data) (or docRef.set(data) in the older SDKs), you update the data for that document and get back a Promise<void>.

When you call addDoc(colRef, data) (or colRef.add(data) in the older SDKs), you create a new document within that collection and get back a Promise<DocumentReference>.

Neither option will return the data you passed in. Instead, you should be listening to the collection containing the new document using onSnapshot(colRef, observer) (or colRef.onSnapshot(observer) in the older SDKs). This observer will have its next() event handler fired with the collection's latest data as a QuerySnapshot object whenever it changes, including when you add data locally using a set/add/update/etc.

While this may seem counter intuitive, this allows you to use the following code to stamp out your collection of documents (and it will stay updated as you add/remove topics):

const db = /* firestore instance (e.g. from parameter) */;

const [topics, setTopics] = useState();
const [errorMsg, setErrorMsg] = useState();

useEffect(() => {
  const colRef = collection(db, "topics");

  return onSnapshot( // <-- onSnapshot creates and returns its unsubscriber
    colRef,
    {
      next: (querySnapshot) => {
        setTopics(
          querySnapshot.docs
            .map(topicDoc => ({
              ...topicDoc.data(), // <- this unwraps the snapshot's data into a normal object, along with its document ID
              id: topicDoc.id
            }))
        );
        setErrorMsg(null);
      },
      error: (err) => {
        setTopics(null);
        // TODO: handle database errors (e.g. permissions, connection loss,  etc.)
        // prefer Firebase error codes over the error's message if available
        // (or the use the error itself if neither is available)
        setErrorMsg(err.code || err.message || err);
      }
    }
  );
}, [db]); // update whenever db is updated

// TODO: use errorMsg/topics as appropriate - such as:

if (topics === undefined) // undefined? still loading!
  return (<div>Loading...</div>);

if (topics === null) // null? you've got an error to display!
  return (<div>Error loading topics: { errorMsg || 'unknown error' }</div>);

if (!topics.length) // empty? show an empty table or other message!
  return (<div>Error: No topics to display</div>);

// something else? good to render!
return (<>{
  topics.map(
    topicData => {
      /* create topic element from topicData (don't forget the key!) */
      return (
        <div key={topicData.id}>
          {topicData.country} - {topicData.name}
        </div>
      )
    }
  )
}</>);
Sign up to request clarification or add additional context in comments.

3 Comments

But how can I implement this after adding the data? Can I merge the onSnapshot with the addDoc? Because the useEffect you showed won't be called after the user
@JoseBernardo I'm not sure I follow. If this listener is attached before you call addDoc, the next() event handler will be fired with the new document when it's called. If this listener is attached after you call 'addDoc', once you attach the listener, the next() even handler will be fired with the new document. In either case, this will mean the topics array now contains the document you just created.
If you need the new document's ID, pull that out of the returned promise: addDoc(colRef, { name, country }).then(docRef => console.log('created ' + docRef.id)). If for some reason you need the new document's data (do not add this to topics yourself, let the listener handle it), you can use: const topicData = { name, country }; addDoc(colRef, topicData).then(docRef => { topicData.id = docRef.id; /* do something with topicData */ }).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.