Cannot figure out why useState hook is not updating properly

Viewed 50

I just want to create a simple component as a Like button. The usage is simple, if the user hits the button his/her username is added to the list (a string) in order to verify this. If hits again the button, his/her username is removed from the string.

import React, { useContext, useEffect, useState } from 'react';

import classnames from 'classnames';

import HeartLike from '../../svg/HearLike/HeartLike';
import { updatePostLike } from '../../api/post';

import './PostShare.css';

const PostShare = ({likedUsers, id}) => {

    const [totalLikedUsers, setTotalLikedUsers] = useState("User 1; User 2;");
    const user = {username: "User 2"};

    useEffect(() => {
        async function updateLike(likedData) {
            const response = await updatePostLike({liked: likedData}, id);
        }

        //save to database
        updateLike(totalLikedUsers);

    }, [totalLikedUsers])

    return (
        <div className="post-share">
            <p onClick={() => {
                totalLikedUsers.includes(user.username) 
                   ? setTotalLikedUsers(likedUsers.replace(user.username + ";", "")) 
                   : setTotalLikedUsers(likedUsers + user.username + ";")
          }}><HeartLike /></p>
        </div>
    );
}

export default PostShare;

With this code, the components starts with the variable totalLikedUsers set to:

"User 1;User 2;"

If User 2 hits de like button, her/his username will be removed:

"User 1;"

But if he/she hits again the button and updating the variable with setTotalLikedUsers, the result is that is added twice when it should only add once:

"User 1;User 2;User 2;"

I have been trying to understand what is happening and I cannot find a solution.

3 Answers
  1. It's better to use array inside useState instead of string:
const [totalLikedUsers, setTotalLikedUsers] = useState(['user1', 'user2']);

And then push / delete item from state.

  1. It's better to send request to updateLike onClick. And on success update the state.

I would break it down a little. Your logic with the names works, if you use the state of the component. Like this.

function App() {
    const [totalLikedUsers, setTotalLikedUsers] = useState("User 1; User 2;");
    const user = {username: "User 2"};

    return (
        <>
            <p>{totalLikedUsers}</p>
            <button onClick={() => {
                totalLikedUsers.includes(user.username)
                    ? setTotalLikedUsers(totalLikedUsers.replace(user.username + ";", ""))
                    : setTotalLikedUsers(totalLikedUsers + user.username + ";")
            }}>
                Click me
            </button>
        </>
    );
}

export default App;

In your code, the likedUsers props (from a parent component) defines the replace though. Make sure, the parent state gets informed, when you clicked the button and persisted the change. You could pass a function to the child as a prop. For example onPress(newLikedUsers) => void

Not disagreeing that you should use array instead of string, I'm just pointing out the error in your code.

Here you are initiating a custom value

const [totalLikedUsers, setTotalLikedUsers] = useState("User 1; User 2;");

However you are using the props likedUsers here

 totalLikedUsers.includes(user.username) 
                   ? setTotalLikedUsers(likedUsers.replace(user.username + ";", "")) 
                   : setTotalLikedUsers(likedUsers + user.username + ";")

The state is not updating as it should because you are mixing state (totalLikedUsers) and props (likedUsers).

You should probably do this instead.

 const [totalLikedUsers, setTotalLikedUsers] = useState(likedUsers); // use your props as initial data

const user = {username: "User 2"};

useEffect(() => {

   setTotalLikedUsers(likedUsers) //update the state when props change

}, [likedUsers])

const onClick = (e) => {
     //instead of using useEffect, handle your updates here.
     const newLikedData =  totalLikedUsers.includes(user.username) 
                   ? totalLikedUsers.replace(user.username + ";", "")
                   : totalLikedUsers + user.username + ";")
     const response = await updatePostLike({liked: newLikedData}, id);
     updateLike(totalLikedUsers) //assuming that likedUsers props is updated here.
}

If you look at the codes above, the state is maybe not necessary, unless you want instantaneous change on the rendering, while waiting for props to update.

Related