CSFrequency / react-firebase-hooks

React Hooks for Firebase.
Apache License 2.0
3.6k stars 306 forks source link

useCollection is working in development mode but not in production #203

Closed MZ071999 closed 2 years ago

MZ071999 commented 2 years ago

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

I'm using the useCollection() hook to retrieve the chat data from firebase.

So the mechanism that I'm trying to achieve is:

  1. user clicks on the "send message" button
  2. the system checks whether or not the chat between user A and user B has already exist
  3. If it doesn't exist, a new chat is created. If it already exists, the old chat is used.

PROBLEM:

When "send message" is clicked, createChat() kicks in, but chatAlreadyExist doesn't work. So firebase always creates a new chat between the two users even though the chat has already exist.

I don't think there's any problem with the firebase connection because a new chat is created every time createChat() is triggered. I believe something is wrong with the useCollection hook but I'm not sure what and how to fix it.

NOTE:

Everything is working as intended in the DEVELOPMENT mode. It is only after deploying to vercel the useCollection hook doesn't seem to work anymore.

This is the code:

import Button from "../UI/Button";
import { Fragment, useState } from "react";
import classes from "./UserProfile.module.css";
import Image from "next/dist/client/image";
import { useSession } from "next-auth/react";
import Modal from "../UI/Modal";
import { dbs } from "../../utils/firebase";
import { useCollection } from "react-firebase-hooks/firestore";
import { useRouter } from "next/dist/client/router";
import StarIcon from "@mui/icons-material/Star";

const UserProfile = (props) => {
  const router = useRouter();
  const { data: session } = useSession();
  const { image, name, email, bio, currentEmail, message, userScores } = props;
  const [modal, setModal] = useState(false);
  const currentUser = email === session?.user.email;
  const Avatar = image;

  let noBio;
  if (bio.trim() === "") {
    noBio = true;
  }

  const userChatRef = dbs
    .collection("chats")
    .where("users", "array-contains", currentEmail);
  const [chatSnapshot] = useCollection(userChatRef);
  const chatAlreadyExists = (email) =>
    !!chatSnapshot?.docs.find(
      (chat) => chat.data().users.find((user) => user === email)?.length > 0
    );
  const createChat = () => {
    if (session) {
      if (!chatAlreadyExists(email)) {
        dbs.collection("chats").add({
          users: [currentEmail, email],
        });
      }
      router.push(`/chat/${email}`);
    }

    if (!session) {
      setModal(true);
    }
  };

  const modalHandler = () => {
    setModal(null);
  };
  return (
    <Fragment>
      {modal && <Modal onConfirm={modalHandler} />}
      <div className={classes.wrapper}>
        <div className={classes.profile}>
          <div className={classes.img}>
            <Image
              src={Avatar}
              layout="fill"
              objectFit="cover"
              alt={"profile"}
            />
          </div>
          <div className={classes.about}>
            <div className={classes.name}> {name} </div>
            {noBio && <div className={classes.bio}> No bio </div>}
            {!noBio && <div className={classes.bio}> {bio} </div>}

            {currentUser && <Button link="/user/edit"> EDIT </Button>}
            {!currentUser && <Button onClick={createChat}> message </Button>}
          </div>
        </div>
        <div className={classes.second}>
          <div className={classes.rewards}>
            <div className={classes.circle}>
              <div className={classes.star}>
                <StarIcon style={{ fontSize: 80 }} />
              </div>
            </div>
            <div>
              {name}
              {"'s Score is:"}
            </div>
            <div className={classes.score}> {userScores} </div>
          </div>

          {currentUser && (
            <div className={classes.message}>
              <span className={classes.bold}>SCORE HISTORY: </span> {message}
            </div>
          )}
        </div>
      </div>
    </Fragment>
  );
};

export default UserProfile;
MZ071999 commented 2 years ago

@chrisbianca would appreciate if you could take a look at this

chrisbianca commented 2 years ago

@MZ071999 this is almost certainly an issue with the way you have structured your code and not the useCollection hook itself. I'm happy to investigate if you can bring a reproducible example demonstrating that useCollection is causing the problem, but otherwise this is a question more suited for Stack Overflow.

MZ071999 commented 2 years ago

@MZ071999 this is almost certainly an issue with the way you have structured your code and not the useCollection hook itself. I'm happy to investigate if you can bring a reproducible example demonstrating that useCollection is causing the problem, but otherwise this is a question more suited for Stack Overflow.

Thank you

I believe it is causing the problem because:

  1. a new chat entry is created by firebase, so the connection with firebase is working
  2. but a new chat is always created, meaning the useCollection hook always returns empty value
  3. when I tried to console log userChatRef, I did get the ref value and the connection to firebase are all defined
  4. when I tried to console log QuerySnapShot returned by useCollection, I got an empty array when it should have returned an array with values

I use the same hook in another component to retrieve the recipient's data but it also doesn't work


function Sidebar(props) {
  const userEmail = props.props[1];
  const { data: session } = useSession();

  const userChatRef = dbs
    .collection("chats")
    .where("users", "array-contains", userEmail);

  const [chatSnapshot] = useCollection(userChatRef);

  useEffect(() => {
    if (session) {
      dbs.collection("users").doc(userEmail).set(
        {
          email: userEmail,
          lastSeen: firebase.firestore.FieldValue.serverTimestamp(),
        },
        { merge: true }
      );
    }
  }, []);

  return (
    <div className={classes.container}>
      <Header />
      {chatSnapshot?.docs.map((chat) => (
        <Recepeint key={chat.id} id={chat.id} users={chat.data().users} />
      ))}
    </div>
  );
}

i console log chatSnapShot and this is what I got:

Screenshot (210)

the array returned is empty. This problem only happens in production, everything is working in development mode.

MZ071999 commented 2 years ago

@MZ071999 this is almost certainly an issue with the way you have structured your code and not the useCollection hook itself. I'm happy to investigate if you can bring a reproducible example demonstrating that useCollection is causing the problem, but otherwise this is a question more suited for Stack Overflow.

Update: the useCollection hook returns value for a very brief second then disappear

chrisbianca commented 2 years ago

@MZ071999 have you checked in the firebase console that the value actually exists? It sounds to me like your writes are being rejected by firestore

MZ071999 commented 2 years ago

@MZ071999 have you checked in the firebase console that the value actually exists? It sounds to me like your writes are being rejected by firestore

yes the value exists, I managed to receive them in development mode.

I have set the rules to allow read and write, I don't think firestore is rejecting the writes because I managed to create a new chat and I can see the value in firestore. It's just the useCollection hook is returning an empty array. I'm still not sure what's going on

MZ071999 commented 2 years ago

update: I tried the code with vanilla useEffect and onsnapshot and I still got an empty array so now I believe the problem is not with useCollection() but probably with the read configuration. I can write but cannot read, that's what I'm getting now