Reputation: 227
Thanks everybody trying to answer my previous question. I've just learned how to mutate boolean in my array objects.
Now I need to trigger re-render since my object changed but don't know much about life cycles. I'm newbie in react so a bit of hand would be perfect. Thanks folks.
export default function App() {
const [obj, setObj] = useState([
{ name: "chicken", comments: "nice", photo: img, boolean: false },
{ name: "beef", comments: "ehh", photo: img, boolean: false },
{ name: "turkey", comments: "not bad", photo: img, boolean: false },
{ name: "broccoli", comments: "cool", photo: img, boolean: false },
{ name: "carrot", comments: "disgusting",photo: img, boolean: false }
]);
const handleClick = (e) => {
const idx = e.target.value;
setObj(prev => {
prev[idx].boolean = !prev[idx].boolean
return prev
});
window.alert(JSON.stringify(obj));
}
return (
<div>
{obj.map((map, key) => (
<div>
<img src={map.photo} alt={map.key} />
<h1>{map.name}</h1>
<p>{`${map.boolean}`}</p>
<p>{key}</p>
<button value={key} onClick={handleClick}>
show comments
</button>
</div>
))}
</div>
);
}
Upvotes: 1
Views: 1126
Reputation: 123
Well, this is actually what you are doing there, you are spreading an array and adding an item after that! what you could do:
const handleClick = (e) => {
const idx = e.target.value;
setObj(prev => {
prev[idx].boolean = true;
return prev;
}
)
console.log(obj);
};
this would work most likely.
also for this kind of huge states, I suggest you consider useReducer
from react.
If I want to complete above:
export default function App() {
const [obj, setObj] = useState([...]);
const [update, setUpdate] = useState(false);
const handleClick = (e) => {
const idx = e.target.value;
setObj(prev => {
prev[idx].boolean = true;
return prev;
}
)
setUpdate(prev => !prev);
};
return (
<div className="main-div">
{obj.map((map, key) => (
<div className="child-div">
<img src={map.photo} alt={map.key} />
<h1>{map.name}</h1>
<p>{map.boolean ? map.comments : null}</p>
<button value={key} onClick={handleClick}>
{map.boolean ? "hide comments" : "show comments"}
</button>
</div>
))}
</div>
);
}
This will make it work.
Upvotes: 1
Reputation: 227
I have finally solved the problem thanks to the contributions of the commenters.
Here is the problem. I tried to change the state, which is an array of objects. I did it because prevstate
works on strings and integer types of states (Increment the number button for instance.) However, things get different when mutating objects and arrays.
If you mutate an object, react wont re-render because spreading array method creates a shallow copy. Thanks to @kmp and others for helping me figure that out.
I had to create a copy of the array to mutate and let react know that state is mutated. The mistaken setStat
code which works to change boolean but doesn't work to re-render is at the bottom of the code commented out.
I learned a lot from this mistake and I really appreciate the guys who contributed. Cheers.
export default function App() {
const [obj, setObj] = useState([
{ name: "chicken", comments: "nice", photo: img, boolean: false },
{ name: "beef", comments: "ehh", photo: img, boolean: false },
{ name: "turkey", comments: "not bad", photo: img, boolean: false },
{ name: "broccoli", comments: "cool", photo: img, boolean: false },
{
name: "carrot",
comments: "disgusting",
photo: img,
key: 4,
boolean: false
}
]);
const handleClick = (e) => {
const idx = e.target.value;
const newObj = JSON.parse(JSON.stringify(obj));
newObj[idx].boolean = !newObj[idx].boolean;
setObj(newObj);
}
return (
<div className="main-div">
{obj.map((map, key) => (
<div className="child-div">
<img src={map.photo} alt={map.key} />
<h1>{map.name}</h1>
<p>{map.boolean ? map.comments : null}</p>
<button value={key} onClick={handleClick}>
{map.boolean ? "hide comments" : "show comments"}
</button>
</div>
))}
</div>
);
}
/*
const idx = e.target.value;
setObj(prev => {
prev[idx].boolean = !prev[idx].boolean;
return prev;
})
window.alert(JSON.stringify(obj));
*/
Upvotes: 0
Reputation: 802
This is what's called a destructuring assignment
[...prev, prev[idx].boolean = true]
You basically say.. Hey, take this object, spread it's values, then assign this new value and append it. The problem with this is that you're creating a new variable prev[idx].boolean
NOT assigning a new value. That's the root of the problem.
Now, what you should do is create this variable outside
const value = { ...prev[idx] }
value.boolean = true
You should create a new object otherwise it's passed by reference.
In Pass by Reference, a function is called by directly passing the reference/address of the variable as the argument. Changing the argument inside the function affects the variable passed from outside the function. In Javascript objects and arrays are passed by reference.
Then assign it
setObj(prev =>
[...prev, value]
)
This means you will override any destructured element with the same value or if it doesn't have any, just assign the new value.
The problem is that you're mutating the state variable
setObj(prev => {
prev[idx].boolean = !prev[idx].boolean
return prev
});
Do this instead
setObj(prev => {
const value = { ...prev[idx] };
value.boolean = !value.boolean;
return [...prev, value];
});
Or this
const value = { ...prev[idx] };
value.boolean = !value.boolean;
setObj([...obj, value]);
In my previous response I've quoted pass by reference.. you should read it carefully, because you're trying to create a shallow copy of an object which wouldn't be a problem if you didn't care about changing the original object as well.
Since you're trying to change the state, you CANNOT change the state variable itself. This is what's happening here
prev[idx].boolean = !prev[idx].boolean
return prev
You have to create a new object to trigger the re-render lifecycle.
Upvotes: 1
Reputation: 71
The issue is here
setObj(prev =>
[...prev, prev[idx].boolean = true]
)
You're actually pushing another boolean value modify handleClick function as such
const handleClick = (e) => {
const idx = e.target.value;// getting the required idx
const newObj = JSON.parse(JSON.stringify(obj)); // creating a new obj
obj[idx].boolean = true; // setting the newobj boolean value
setObj(newObj); // useState function to set new state.
};
Upvotes: 1