The initial value in useEffect is not being updated by useState

I am currently implementing a like feature for my web application. The issue lies in the fact that my setLike function is not updating the state even after using setLike(!like). I verified this by adding console.log() statements before and after the setLike() call, but both logs showed the same value. Below is my Post component.

import React , {useState,useEffect} from 'react'
import './Post.css'
import Avatar from '@material-ui/core/Avatar';
import {Button, IconButton, Input, Typography } from '@material-ui/core';
import {DataBase} from './firebase'
import firebase from 'firebase';
import FavoriteIcon from '@material-ui/icons/Favorite';
import {useStateValue} from '../contexts/StateProvider'


function Post({postId}) {

   //get the user from the provider
   const [{user}, dispatch] = useStateValue();
   //number of likes
   const [likeCount,setLikeCount] = useState(likesCount)
   //if like=true or not
   const [like, setLike] = useState(false);

My back-end uses Firebase Firestore database. The like document is only created in the database collection when a user interacts with it for the first time (liking it, unliking it, then liking it again). So, the first step is to check if the user has previously liked the document. If they have, retrieve the like value (true/false) for that specific user and set it using setLike(like). Also, fetch the number of documents in the collection where like==true and update the like count using setLikeCount(likeCount).

//=======Get likes from the database ===========================
useEffect(() => {       
//check if the user already liked the doc or not  (first time or not)
DataBase.collection('posts').doc(postId).collection('postLikes')
.doc(user.uid).get().then((doc) => {
            if (doc.exists) {
                console.log(doc.data().like)
                //set like to value of like from database
                setLike(doc.data().like)

            } else {
                // doc.data() will be undefined in this case
                console.log("Not liked");
            }
        }).catch((error) => {
            console.log("Error getting document:", error);
        });


//grab the docs which have like=true 
DataBase.collection('posts').doc(postId).collection('postLikes').where("like", "==", 
true).get()
            .then((querySnapshot) => {
                setLikeCount((querySnapshot.docs.map(doc =>doc.data())).length)
                console.log(likeCount +" likes count")
            })
            .catch((error) => {
                console.log("Error getting documents: ", error);
            });
            

          
}

//when postId changes or page loads fire the code above
},[postId])

Here is my postLike function. The intention of setLike(!like) is to toggle the user's previous like status for the document stored in Firebase. However, it seems that the update is not taking place.

//=============Post likes to the database=======

const postLike = (e) => {
   //if already liked i.e. doc exists
   console.log(like+"like before")
   setLike(!like)
   console.log(like+"like after")
   like?(setLikeCount(likeCount+1)):(setLikeCount(likeCount-1))
   console.log("likeCount"+likeCount)
   DataBase.collection('posts').doc(postId).collection('postLikes').doc(user.uid).set(
    { 
        like:like,
        username:user.displayName,
        timestamp:firebase.firestore.FieldValue.serverTimestamp(),

    }
  ).catch((err)=>{console.log("something wrong happened "+err.message)})


}
                          

return (
   <div className="post">
        <div className="post__likes"> 

                         {/*like button*/}                                              
                                                                                                     
 {
  like?
  (<Button onClick={postLike} ><FavoriteIcon   fontsize="small" cursor="pointer" onClick=. 
  {postLike} style={{color:'red'}}/></Button> ):
  (<Button onClick={postLike} ><FavoriteIcon   fontsize="small" cursor="pointer"  /> 
   </Button>)                                       
}
                                       
         <Typography style={{color:'aliceblue'}}>Liked by {likeCount}</Typography>
          </div>

       </div>
   )
}

export default Post

Answer №1

Note:

After reconsidering, it seems using useEffect may not have been the best approach in this case. The function is being called both on initial load and after the like value is updated from the server request. I've put together a code snippet that achieves most of the desired functionality. However, it's worth noting that there is a potential issue where a user can click the like button before the likeCount data is retrieved from the server. One possible solution could be to disable the button while the request is pending.

const {
  useState,
  useEffect
} = React;


function Post({
  postId
}) {
  //number of likes
  const [likeCount, setLikeCount] = useState(5)
  //if like=true or not
  const [like, setLike] = useState(false);

  //=======Get likes from the database ===========================
  useEffect(() => {
    // This is a call to the server to check if the user already liked the doc or not  (first time or not)
    setTimeout(() => {
      if (true) {
        setLike(true);
      }
    }, 500);


// This is a call to the server to grab the docs which have like=true
    setTimeout(() => {
      setLikeCount(15);
    }, 400);

    //when postId changes or page loads fire the code above
  }, [postId]);

  //=============Post likes to the database=======

  const postLike = (e) => {
    //if already liked i.e. doc exists
    const newLikeValue = !like;
    const newLikeCount = like ? likeCount - 1 : likeCount + 1;
    setLike(!like);

    setLikeCount(newLikeCount);
    setLike(newLikeValue);
    
    // Update like value on server here
    /*
    DataBase
      .collection('posts').doc(postId)
      .collection('postLikes').doc(user.uid).set({
        like: newLikeValue,  // <-- Use newLikeValue here
        username: user.displayName,
        timestamp: firebase.firestore.FieldValue.serverTimestamp(),

      }).catch((err) => {
        console.log("something wrong happened " + err.message)
      })
    */
  }


  return (
    <div className = "post" >
      <div className = "post__likes" > { /*like button*/ }
        {
          like
            ? (<button onClick={postLike}><div style={{color:'red'}}>Icon here</div></button>)
            : (<button onClick={postLike}><div>Icon here</div></button>)
        }
      <p>Liked by {likeCount}</p>
      </div>
    </div>
  );

}

ReactDOM.render(
  <Post postId={5}/>,
  document.getElementById("react")
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="react"></div>


Original advice:

In my opinion, using a local copy as suggested by Dave would be a good strategy. Another option would be setting up an additional useEffect hook to monitor changes in the like state and trigger the server update from there.

useEffect(() => {
  like?(setLikeCount(likeCount+1)):(setLikeCount(likeCount-1));
  DataBase
    .collection('posts').doc(postId)
    .collection('postLikes').doc(user.uid)
    .set(
      { 
        like:like,
        username:user.displayName,
        timestamp:firebase.firestore.FieldValue.serverTimestamp(),

      }
    ).catch((err)=>{console.log("something wrong happened "+err.message)})

}, [like]);


const updateLike = (e) => {
  setLike(!like);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Preserve multiple selected values post form submission using PHP, HTML, and JavaScript

How can I retain the selected values in a form after it is submitted? My current code functions correctly when only one value is selected, but does not maintain all selected values when multiple are chosen simultaneously. Any assistance would be apprecia ...

What is the fastest method for applying a Material-UI theme?

Exploring the custom theming possibilities in react-admin has been fascinating to me. Is there a way in react-admin to easily incorporate a custom theme without manually applying it component by component? I'm thinking along the lines of Material Ui ...

Converting JSON data into an array of JavaScript objects

Here is some of the JSON data I have: [ { "items": [ { "retailername": "Zop Now", "value": 475 }, { "retailername": "Snap Deal", "value": 265 ...

In order to manage this file type properly, you might require a suitable loader, such as an arrow function within a React

Currently, I am in the process of creating an npm package and have encountered a difficulty with the following code: You may need an appropriate loader to handle this file type. | } | | holenNummerInSchnur = Schnur => { | if (this.beurte ...

Tips on personalizing MUI X Data Grid Toolbar to exclusively display icons

`function EnhancedToolbar() { return ( <GridToolbarContainer> <GridToolbarIcon icon={<FilterListIcon />} /> <GridToolbarIcon icon={<ViewColumnIcon />} /> <GridToolbarIcon icon={<SettingsEthernetIc ...

Create custom error messages for loopback instead of using the default ones

I am attempting to customize the default error messages provided by loopback. Here is my approach: server/middleware.json: { "initial:before": { "loopback#favicon": {} }, "initial": { "compression": {}, "cors": { "params": { ...

Issue with accessing container client in Azure Storage JavaScript library

When working on my Angular project, I incorporated the @azure/storage-blob library. I successfully got the BlobServiceClient and proceeded to call the getContainerClient method, only to encounter this error: "Uncaught (in promise): TypeError: Failed ...

React 17 Form not registering the final digit during onChange event

I am currently experiencing an issue with a form that includes an input field of type "number." When I enter a value, the last number seems to be skipped. For example: If I input 99 into the box, only 9 is saved. Similarly, when typing in 2523, only 252 ...

Troubleshooting an H20 error status when deploying a Next.js app on Heroku

I am relatively new to working with React and Next.js. I successfully developed a simple website in Next.js, and when I built the project on my local system using `npm run build`, it ran without any errors. However, when I tried to deploy it on Heroku, the ...

Why doesn't the div click event trigger when the mouse hovers over an iframe?

My dilemma involves a div element with a click event. When the div is positioned over an iframe area (closer to the user than the iframe), the click event fails to trigger. However, if the div is located elsewhere and not above the iframe, the click event ...

Unable to append elements to material-ui form

I received a hand-me-down project that requires me to include additional elements in a form built with react and material-ui. While I can successfully implement text fields, integrating a dropdown menu results in the selected value not being retained. I&a ...

Reset checkboxes in Material UI data grid

Currently, I am immersed in a React Js project that involves various tabs, each containing a unique data grid table with rows featuring checkboxes. Issue: Whenever I select a row from Table 1 and navigate to Tab 2 before returning to Tab 1, the checkboxes ...

Ways to retrieve the mapState property within a method

Is there a way to access the count property within the method while working with vuex? Take a look at my code provided below: Screenshot of Code: https://i.stack.imgur.com/xNUHM.png Error Message [Vue warn]: Computed property "count" was assigned to bu ...

What causes the difference in behavior between using setInterval() with a named function as an argument versus using an anonymous function?

I can't seem to figure out why using the function name in setInterval is causing issues, while passing an anonymous function works perfectly fine. In the example that's not working (it's logging NaN to the console and before the first call, ...

Displaying a React component within a StencilJS component and connecting the slot to props.children

Is there a way to embed an existing React component into a StencilJS component without the need for multiple wrapper elements and manual element manipulation? I have managed to make it work by using ReactDom.render inside the StencilJS componentDidRender ...

Puppeteer exhibiting unexpected behavior compared to the Developer Console

My goal is to extract the title of the page using Puppeteer from the following URL: Here's the code snippet I am working with: (async () => { const browser = await puppet.launch({ headless: true }); const page = a ...

Running Protractor tests can be frustratingly sluggish and frequently result in timeouts

After spending most of the afternoon struggling with this test, I've tried different approaches but none seem to work. The task at hand is searching for users within the company, generating a table, and selecting the user that matches the name. Curren ...

What are the differences in how a link tag responds on mobile devices?

I have a question regarding opening a pdf in an iframe on the current page. Is there a way to handle this differently for mobile/tablet users? For example, clicking the link could open it in another tab for better readability instead of within the iframe ...

JSON nested error: Cannot read property 'length' of undefined

Having some trouble working with a nested array within a JSON in D3JS, specifically encountering a "property length undefined" error at the line: .attr("d", function(d) { return line(d.points); }). Below is the JSON data structure: [ { "aspectRatio" ...

HTML anchor tag failing to redirect to specified link

I need to populate the URI property from an API result into an HTML format. I attempted to achieve this by calling a function and running a loop 3 times in order to display 3 links with the URI property of each object. However, the code is not functioning ...