cahe1540
cahe1540

Reputation: 47

How to synchronize data in a React app? The data rendered doesn't match the data accessed in the function call

I'm making a React app that requests data from an API and renders the results as cards. The issue I'm having is that I added a 'favorite' star icon to each card, that when clicked, will become highlighted gold, and an additional click will revert it to grey. This is controlled by a Boolean value stored in a React Hook for the local component. The color change works properly, however when I want to access the boolean state variable to continue the rest of the operation, the local state value that I keep accessing seems to be the 'previous' state. I can't figure out how to fix this.

Reading up on the documentation leads me to think that I'm accessing stale state data or stale closures. I'm still having trouble fixing this so I would appreciate some assistance, here's what I have.

    import React, { useState } from 'react'
    import { FontAwesomeIcon }  from '@fortawesome/react-fontawesome'
    import { faStar} from '@fortawesome/free-solid-svg-icons'
    
    export const Card = ({ item, addOn, onDelete }) => {
        //flags if the star was clicked or not
        const [favored, setFavored] = useState(false);
    
        //update favorite component
        const updateFavorites = (item) => {
    
            //here thet favored state is toggled 
            setFavored(!favored);
    
            //further attempts to access favored results in the wrong favored being accessed
            if(favored){
                console.log(`value of favored is: ${favored}, the start should be gold`);
            }else{
                console.log(`value of favored is: ${favored}, the star should be grey`);
    
            }
        }
        
        return (
            <div className = 'card' key = {item.id} >
                <FontAwesomeIcon
                    icon = {faStar}
                    className = {favored ? 'star favored' : 'star'}
                    onClick = {(e) => updateFavorites(item)}
                />
                <div className = 'card-info'>
                    <img
                        src = {item.image_url}
                        alt = ''
                        style = {{height: '15rem', width: '5rem'}}
                    />
                    <div>
                        <h3>{item.name}</h3>
                        <p className = "description">{item.description}</p>
                    </div>
                </div>
            </div>
        )
    }

Edit: I solved my issue by doing the following:

const updateFavorites = (item) => {
        //here thet favored state is toggled
        setFavored(favored => {
            favored = !favored;
            if(favored){
                console.log('adding a favorite');
                addOn(item);
            }else{
                console.log('removing a favorite');
                onDelete(item);
            };
            return favored;
        }); 
    }

However, I replaced it by using useEffect as per the answers.

Upvotes: 1

Views: 143

Answers (2)

jharris711
jharris711

Reputation: 612

You should import useEffect and move the logic from the event handler to the useEffect hook:

import React, { useState, useEffect } from 'react'
import { FontAwesomeIcon }  from '@fortawesome/react-fontawesome'
import { faStar} from '@fortawesome/free-solid-svg-icons'
    
export const Card = ({ item, addOn, onDelete }) => {
    const [favored, setFavored] = useState(false);
    
    const updateFavorites = () => {
        setFavored(!favored);
    }

    // Move the desired logic here and add 'favored' and 'item' to your dependency array:
    useEffect(() => {
        if(favored){
            console.log(`value of favored is: ${favored}, the start should be gold`);
        }else{
            console.log(`value of favored is: ${favored}, the star should be grey`);
        }
    }, [favored, item])
        
    return (
        <div className = 'card' key = {item.id} >
            <FontAwesomeIcon
                icon = {faStar}
                className = {favored ? 'star favored' : 'star'}
                onClick = {() => updateFavorites()}
            />
            <div className = 'card-info'>
                <img
                    src = {item.image_url}
                    alt = ''
                    style = {{height: '15rem', width: '5rem'}}
                />
                <div>
                    <h3>{item.name}</h3>
                    <p className = "description">{item.description}</p>
                </div>
            </div>
        </div>
    )
}

Upvotes: 1

sarali Rajeshuni
sarali Rajeshuni

Reputation: 36

Fact is that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created. Now in the current state, the values are obtained by existing closures, and when a re-render happens, the closures are updated based on whether the function is recreated again or not.

You can use following solutions.

useEffect(() => {
    if (favored) {
      console.log(`value of favored is: ${favored}, the start should be gold`);
    } else {
      console.log(`value of favored is: ${favored}, the star should be grey`);
    }
  }, [favored,item]);

Upvotes: 2

Related Questions