0

I have a next.js application in which I have a real-time chat feature.

A get() request is supposed to be sent to firestore to check if chat between user A and user B already exists. If it does not already exist, a new chat between A and B is created

Problem:

The get() request to firestore always returns an empty value. So a new chat room between user A and user B is always created.

Since a new chat room is created successfully, I can write to firestore but cannot read data.

the code:

import { useEffect, useState } from "react";
import { dbs } from "../utils/firebase";

function Test() {
  const [chats, setChats] = useState([]);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await dbs
          .collection("chats")
          .where("users", "array-contains", "some@email")
          .get();
        setChats(response);
      } catch (err) {
        console.log(err);
      }
    };
    fetchData();
  }, []);
  console.log("chat is", chats);

  return <div></div>;
}

export default Test;

What I have attempted and discovered:

  • I deployed an entirely new next.js app (let's call it app B) and tried to get data from the same firestore project with the same code above and it works. So my firestore configuration is fine, the code is fine, and there's nothing wrong with the deployment server.
  • Since there's nothing wrong with firestore or the deployment server or the code, I think something is wrong with the next.js app itself and the dependencies surrounding it. I don't know why it works in app B and not in app A.

Note, in App A:

  • Everything works fine in development mode, I can get the data.
  • I'm also using mongoDB and other third-party APIs in this app and I can successfully read and write data to these APIs. Only the get() request to firestore is not working. which made me think the problem must be with my firestore config but app B proves that's not the case.
  • I'm using google OAuth with mongoDB and next-auth to authenticate my users in app A. In app B I'm only using google OAuth without the DB.

I could show the code for both app A and app B but they are exactly the same. The code under the _app.js file is the same, the way I structure it is also the same. In both app A and app B, I called the get() request to firestore from a Test.js component in a test.js page but the get() request only returns value in app B and always returns null in app A.

So basically the only difference is app A is an actual app with plenty of dependencies, libraries, and files. app B is just a test app.

My question now is:

  • Could the read operation to firestore be affected by other dependencies or npm libraries in the project? or could it be affected by using another DB to get the auth session context?
  • Why is the write operation successful but not the read?
  • Why does it work in dev mode but not in prod mode? is there any difference between retrieving data from firestore in dev mode vs prod mode?
MZt
  • 11
  • 2
  • Linking [deleted question](https://stackoverflow.com/q/70887667/3068190) for other users. In future, edit your existing question rather then post the same question again, editing a question will bump it back to the top of the active questions feed. – samthecodingman Jan 29 '22 at 11:12
  • @samthecodingman didn't know that, i thought i'm supposed to create a new one as the problem i discovered turned out to be different to the prev question and the comments under that question won't be relevant anymore but thanks for pointing that out – MZt Jan 29 '22 at 11:30
  • @samthecodingman so speaking of the question, what do you think causes this problem? :( – MZt Jan 29 '22 at 11:34
  • Patience is a virtue. Remember that StackOverflow is volunteer driven. If you need instant answers, hire a freelancer. – samthecodingman Jan 29 '22 at 11:48

1 Answers1

1

As explained yesterday, you aren't paying close enough attention to the intermediate states properly and it is likely that NextJS is tripping up because of it.

On the first render of your current code, chats is an empty array. Even so, once the promise fulfills, you update chats to a QuerySnapshot object, not an array.

import { useEffect, useState } from "react";
import { dbs } from "../utils/firebase";

function Test() {
  const currentUserEmail = "some@email"; // get from wherever

  const [chatsInfo, setChatsInfo] = useState({ status: "loading", data: null, error: null });

  useEffect(() => {
    let unsubscribed = false;

    const fetchData = async () => {
      return dbs
        .collection("chats")
        .where("users", "array-contains", currentUserEmail)
        .get();
    };

    fetchData()
      .then(() => {
        if (unsubscribed) return; // do nothing
        setChatsInfo({
          status: "loaded",
          data: response.docs, // QueryDocumentSnapshot[]
          error: null
        });
      })
      .catch((err) => {
        if (unsubscribed) return; // do nothing
        setChatsInfo({
          status: "error",
          data: null,
          error: err
        });
      });
    };
    // return a unsubcribe callback that makes sure setChatsInfo
    // isn't called when the component is unmounted or is out of date    ​
    ​return () => unsubscribed = true; 
 ​ }, [currentUserEmail]); // rerun if user email changes
​ 
​  const { status, data: chats, error } = chatsInfo;
  console.log(JSON.parse(JSON.stringify({ status, chats, error }))); // crude object copy

  switch (status) {
    case "loading":
      return (<div>Loading...</div>);
    case "error":
      return (
        <div class="error">
          Failed to load data: {error.code || error.message}
        </div>
      );
  }

  // if here, status is "loaded"

  if (chats.length === 0) {
    // You could also show a "Message Someone" call-to-action button here
    return (
      <div class="error">
        No chat groups found.
      </div>
    );
  }

  // stamp out list of chats
  return (<>
    {chats.map((chatDoc) => {
      return (
        <div key={chatDoc.id}>
          {JSON.stringify(chatDoc.data())}
        </div>
      );
    })}
  </>);
}

export default Test;

Notes:

  • A decent chunk of the above code can be eliminated by using an implementation of useAsyncEffect like @react-hook/async and use-async-effect. These will handle the intermediate states for you like loading, improper authentication, unmounting before finishing, and other errors (which are all not covered in the above snippet). This thread contains more details on this topic.
  • I highly recommend not using email addresses in their raw form for user IDs. With the current structure of your database, anyone can come along and rip all the emails out and start spamming them.
  • Each user should have some private identifier that doesn't reveal sensitive information about that user. This could be a Firebase Authentication User ID, the user's email address hashed using md5 (which also allows you to use Gravatar for user profile pictures) or some other ID like a username. Once you have such a user ID, you can use the approach outlined in this thread for handling messages between users.
samthecodingman
  • 23,122
  • 4
  • 30
  • 54
  • thank you, I tried to run this on dev mode and it says `Failed to load data: response is not defined` – MZt Jan 29 '22 at 12:16
  • @MZt Probably a simple typo, you should be able to add `response` in `fetchData().then((response) => { ... })` to solve the issue. – juliomalves Jan 29 '22 at 18:14
  • @juliomalves okay, it works on dev mode. But again, when I tried on prod mode it says `no chat groups found` so I'm having the exact same problem again. firestore is not returning any data on prod mode. ahhh this is sooo confusing – MZt Jan 30 '22 at 01:33
  • I don't think the code is the problem here, I have also tried retrieving data with `react-firebase-hooks` but the problem is the same: firestore always returns null on prod mode. Things are fine on dev mode. – MZt Jan 30 '22 at 01:35
  • and to even add the confusion, the same exact code works in app B (prod mode). So my firestore config is fine and the deployment server is also fine. – MZt Jan 30 '22 at 01:37
  • it's almost like firestore is blocking read access specifically from the deployed app A (is that even possible)? – MZt Jan 30 '22 at 01:44
  • @MZt When you run in "dev mode", is it running everything using the Firestore emulator? If so, your production needs data uploaded to it first. – samthecodingman Jan 30 '22 at 01:46
  • @samthecodingman no, I never run it on the emulator. And (with the same firestore dbs) I do manage to get the data in prod-mode app B. The problem is only with prod-mode app A. – MZt Jan 30 '22 at 02:01