'Creating like button using firestore and useEffect/UseState is causing a loop [duplicate]

This is the code for a component I created that gets loaded into some cards that display data. This component loads the data from that card (sale_id) and is used to store on the DB.

import { LikeButtonStyle } from './LikeButton.styled';
import { Image } from '../../styles/index.styled';
import { useEffect, useState } from 'react';
import { useAuthContext } from '../Provider';
import { setDoc, doc, getDoc } from 'firebase/firestore';
import { db } from '../../firebase/clientApp';

function UserLikeButton(sale_id) {
  const { currentUser } = useAuthContext();
  var [userLikes, setUserLikes] = useState('no');
  var [user, setUser] = useState(currentUser.actor);
  const [likeCount, setLikeCount] = useState('0');
  const id = sale_id.sale_id;

  const userCollectionRef = doc(db, 'Feed Posts', id);

 

  async function getUserData() {
    const docSnap = await getDoc(userCollectionRef);
    if (docSnap.exists()) {
      setUser(docSnap.data().username);
      setUserLikes(docSnap.data().likePost);
      if (userLikes === 'no') {
        setLikeCount('0');
      } else {
        setLikeCount('1');
      }
    }
  }

  useEffect(() => {
    getUserData();
  }, );

  const addInfoToDataBase = async () => {
    await setDoc(userCollectionRef, {
      likePost: userLikes,
      username: user,
    });
  };

  function likeUnlike() {
    if (userLikes === 'no') {
      setUserLikes('yes');
      setLikeCount('1');
      addInfoToDataBase();
    } else {
      setUserLikes('no');
      setLikeCount('0');
      addInfoToDataBase();
    }
  }

  if (!currentUser) {
    return (
      <LikeButtonStyle>
        <Image width="10px" height="auto" src="/like-svgrepo-com.svg"></Image>
        <> {userLikes} </>
      </LikeButtonStyle>
    );
  } else {
    return (
      <>
        <LikeButtonStyle onClick={likeUnlike}>
          <Image width="10px" height="auto" src="/like-svgrepo-com.svg"></Image>
          <> {likeCount} </>
        </LikeButtonStyle>
      </>
    );
  }
}

export default UserLikeButton;

The logic that I have is that on the database, each post (sale_id) contains a user and their decision on liking an item which is stored as userLike. I use useEffect to load whatever is on the database on render running a function called getUserData and then this is set in state. This works.

Then, on another operation, when the LikeButtonStyle button is clicked, it runs likeUnlike function which first checks what the state is then it sets it to the opposite, then it writes that in the database. Both of these work independently but when paired up what happens is that it will change the counter to the opposite, then back immediately to what's on the DB.

As a solution I tried to add an empty array dependency in the useEffect that calls getUserData and that does work but when I refresh, all likes are back at 0 (default).



Solution 1:[1]

You need to add an empty dependency array to your useEffect. This makes sure it's only called once when the component mounts. If you tried this like you said and when you refresh the page and your "likes" are being reset then it seems that they aren't being saved correctly in your database when addInfoToDataBase() runs.

 useEffect(() => {
   getUserData();
 }, []);

What's happening here is that every time you update state in your likeUnlike function the useEffect is running again. Which is why its' showing what's in your DB which either isn't being updated properly or there's a race condition and the GET request is returning before your POST request completes.

Solution 2:[2]

I see some issues in the code. one is that you are enabling user to mutate state and then you are updating the same state from within an effect which is triggered by update of the state. there is a loop getting created over here.

I would recommend following changes that can help avoid race conditions and encourage predictable component behavior.

  1. read data from userCollectionRef when passing initial state via. useState for userLikes.
  2. setting initial likeCount could be derived from userLikes state.
  3. useEffect to only update data in dataBase.

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 CaseyC
Solution 2 AppleCiderGuy