Reputation: 47
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
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
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