anulamatamang
anulamatamang

Reputation: 23

REACT + FIRESTORE : Listen to changes in firebase collection in a react component to automatically update the react state

I have a posts collection in firestore from which I am getting all posts created by users in a list [user1, user2, ...]. I can listen to changes in collection using onSnapshot in the method below:

export default function Feed({ friendList }) {
  // currentUser context
  const { currentUser } = useAuth();

  // array to save posts from database
  const [posts, setPosts] = useState([]);

  // function to fetch data from database
  useEffect(() => {

    db.collection('posts')
      .where('userId', 'in', [...friendList])
      .orderBy('timestamp', 'desc')
      .onSnapshot((snapshot) => {
        setPosts(
          snapshot.docs.map((doc) => ({
            id: doc.id,
            post: doc.data(),
          }))
        );
      });
 }, []);

  return (
    <div className='feed'>
      {currentUser ? (
        <div className='feed_container'>
          {posts.map(({ id, post }) => {
            return (
              <Post
                key={id}
                id={id}
                userProfileURL={post.userProfileURL}
                username={post.username}
                displayName={post.displayName}
                photoURL={post.photoURL}
                />
            );
        })}
      </div>

But this 'in' query wont work if the friendList has more than 10 elements. So I can get the required documents from the collction using promises like:

useEffect(() => {
    getPostsFromFriendsList(friendList);
}, []);

async function getPostsFromFriendsList(friendList){
    const promises = [];
    for (let i = 0; i < friendList.length; i++) {
      promises.push(getPosts(friendList[i]));
    }

    const postsForHomeFeed = await Promise.all(promises);
    const array = [];
   
    for (let i = 0; i < postsForHomeFeed.length; i++) {
      postsForHomeFeed[i].docs.map((doc) => {
        array.push({
          id: doc.id,
          post: doc.data(),
        });
      });
    }
   setPosts(array);
}
  function getPosts(userId) {
    return new Promise((resolve, reject) => {
      db.collection('posts')
        .where('userId', 'in', [userId])
        .onSnapshot((snapshot) => {
          resolve(snapshot);
        });
    });
  }

But I no longer can listen to the changes in database to update my react state to render accordingly if a post was added or deleted in my firestore collection. How can I setup my useEffect for the promises solution in the second code such that I can update my react state everytime there is change in the firestore collection? Thank you.

Upvotes: 1

Views: 3249

Answers (2)

Akshay K Nair
Akshay K Nair

Reputation: 1476

Use the onSnapshot in useEffect block. Simple example;

useEffect(() => {
    onSnapshot(collection(db, "profiles"), (snapshot) => {
      setProfiles(snapshot.docs.map((doc)=>({...doc.data(), id: doc.id})))
    })
    console.log(profiles);
});

The profiles state will contain updated items all the time.

Upvotes: 1

Tarik Huber
Tarik Huber

Reputation: 7388

You would need to leave the listeners open and update the state each time they are triggered and merge the data according to the post ids like here:

useEffect(() => {
  getPostsFromFriendsList(friendList);
}, []);

//source: https://stackoverflow.com/questions/7146217/merge-2-arrays-of-objects
const merge = (a, b, prop) => {
  var reduced = a.filter(
    (aitem) => !b.find((bitem) => aitem[prop] === bitem[prop])
  );
  return reduced.concat(b);
};

async function getPostsFromFriendsList(friendList) {
  const promises = [];
  for (let i = 0; i < friendList.length; i++) {
    db.collection("posts")
      .where("userId", "in", [friendList[i]])
      .onSnapshot((snapshot) => {
        const array = [];
        snapshot.docs.map((doc) => {
          array.push({
            id: doc.id,
            post: doc.data(),
          });
        });

        setPosts(merge([...posts], array, "id"));
      });
  }
}

Upvotes: 1

Related Questions