As stated in documentation of hook useState
, React uses the static method Object.is
to determine whether two values are equal. The component will only be updated if the unevenness between the values is confirmed.
The comparison made by Object.is
is very similar to the operator ===
. The difference is that, for Object.is
, two values NaN
are considered equal and +0
and -0
are considered different.[1]
In this way, we need to remember that, for the ===
(and therefore to the Object.is
also), two objects are equal if they have the same reference. See:
const obj = { name: 'Foo' };
const b = obj; // Mesma referência.
console.log(Object.is(obj, b)); //=> true
// Mesmo se alterarmos em `b` (ou em `obj`), a igualdade manter-se-á:
b.name = 'Bar';
console.log(Object.is(obj, b));
Now see what you’re doing in your component:
const newData = data;
newData.count = newData.count + 1;
setData(newData);
I can see the problem, all right? :)
See what you’re assigning to newData
the reference of the object data
(the object assignment does not create a "copy", only the reference is assigned). Thus, you do not have a new object, but the same object!
Therefore, even if you modify one of the properties of the object newData
(which is the same as data
), the algorithm of Object.is
still determine that they are the same object of the previous state (since, as we have seen, this comparison is made based on the reference).
Finally, to correct, you must ensure that you are passing a new object (i.e., of different reference) to the function setData
.
One way to do this is to use the Operator spread to spread the properties in a new literal (as suggested to another answer):
const handlerIncrementData = () => {
const { count } = data;
count++;
// Note abaixo que um **novo** literal está sendo criado (portanto, diferente referência):
setData({ ...data, count });
};
Another option is to use Object.assign
, passing a new object in its first argument:
const handlerIncrementData = () => {
// Novo objeto sendo utilizado como "base":
// ↓↓
setData(Object.assign({}, data, { count: data.count + 1 }));
};
In short: for the component to be rendered again, you must ensure that the value you are passing is different from the previous value (according to the comparison of Object.is
). In the case of objects, it is sufficient to ensure that the reference is different.
This is precisely why it works if you use a primitive rather than an object. Primitives are compared by value; while objects, by reference.
To learn more about the differences about the Operator spread in objects and Object.assign
, read What is the difference between Object.assign and spread Operator?.
Thank you very much, I’m doing a bit of a big application and it took me a while to realize that some components weren’t working, I was afraid it was too late to change something, anyway, really worth.
– Lúcio Carvalho