It may be my fault, but the issue seems exist.
16.8.6
React useState Hook don't rerender the changes #15595
useState
to create a state which is an array of object.jsx
const onChangeData = (key, value, id) => {
state.forEach((a, i) => {
if (a.id === id) {
state.splice(i, 1, { ...a, [key]: value });
}
});
setState(state);
};
jsx
const onChangeData = (key, value, id) => {
const temp = data;
temp.forEach((a, i) => {
if (a.id === id) {
temp.splice(i, 1, { ...a, [key]: value });
}
});
setState(temp);
};
useEffect
to listen the state updating and do something, but it doesn't work:jsx
useEffect(()=>{
// do something
}, [state]);
jsx
const onChangeData = (key, value, id) => {
let temp = [];
temp = temp.concat(state);
temp.forEach((a, i) => {
if (a.id === id) {
temp.splice(i, 1, { ...a, [key]: value });
}
});
setState(temp);
};
So I guess React checks the first layer of the array state only (own properties? ), or checks the memory stack of the state only, no deeper comparing. (Just my guess)
I am not sure the reason of the issue, maybe it is my fault, maybe it is a solution for the performance. I hope that someone can tell me the real reason, and how to use it exactly.
Thanks in advance for anybody who answers me!
Your first approach doesn't work because of this https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update.
If we look at the code you have written:
const onChangeData = (key, value, id) => {
state.forEach((a, i) => {
if (a.id === id) {
state.splice(i, 1, { ...a, [key]: value });
}
});
setState(state);
};
and the fact that useState tries bailing out the updates when the state is the same then we will see that your setState calling receives the same array reference.
And when you work with react states then don't use methods which modify things (like objects, arrays etc) in place. In your example splice method modifies state
in place. Instead I would write it like this:
const onChangeData = (key, value, id) => {
const nextState = state.map(a => a.id === id ? { ...a, [key]: value } : a);
setState(nextState);
};
@dmytro-lymarenko I think I understand the array.map
way you showed.
@dmytro-lymarenko So according to the introduction which you sent me, React hooks use Object.is
to compare the old state and the current one. For this reason, it won't to check deeper. Ok, got it. Thanks for your help, thanks a lot. 馃
@JandenMa yes, so, in order to be sure that always perform the rendering, you need to be sure to pass to the hook a new instance, in my case I found practical use spread operator for that: setStateMyList([..stateMyList])
. (of course this make sense if you use method mutators against stateMyList
instead of accessor method to override it, so change their instance).
@Jero786 Ya, I am with you on that! Thx!
If you want your hook to detect state change on an array, you need to copt the previous array,
try using Array.from() when getting your array and then set the new array state value
Object.is
doesn't seem to look deep enough. I ran into a problem when updating parent state via a child two layers deep.
For example, say you have an object Dog
that has a property ears
of type strings
.
Object.is
says that the dog
and nextDog
objects are equal, despite the fact that nextDog
is modified.
// parent
const [dog, setDog] = useState<Dog>(DefaultDog);
// child's child
const nextDog = dog;
nextDog.ears = [...nextDog.ears, "another ear"];
changeDogFromChildsChild(nextDog);
The only solution is to do something weird, such as using const nextDog = Object.assign({}, dog)
or JSON.stringify
immediately followed by a JSON.parse
. This really seems quite dumb really.
Not sure if there's really a good way around it though.
@JandenMa I know this issue is since closed, but I would like to add my input as I came across similar issue. Taking from @dmytro-lymarenko's response, useState()
would like to keep things immutable and its good practice to always create a copy of the array/object, currently "watched" by useState
:
const onChangeData = (key, value, id) => {
let nextState = [...state]
nextState.key = 'someNewKey' // you can set whatever you desire
setState(nextState);
};
@tochidlife For future Googlers, I think you meant nextState[key] = value
in this particular case :)
And when you work with react states then don't use methods which modify things (like objects, arrays etc) in place. In your example splice method modifies
state
in place. Instead I would write it like this:const onChangeData = (key, value, id) => { const nextState = state.map(a => a.id === id ? { ...a, [key]: value } : a); setState(nextState); };
Thank you @dmytro-lymarenko :)
Most helpful comment
And when you work with react states then don't use methods which modify things (like objects, arrays etc) in place. In your example splice method modifies
state
in place. Instead I would write it like this: