Я столкнулся со своеобразной проблемой с обновлениями состояния в React, это не ошибка, мой код работал нормально, но я чувствую себя немного странно. Изначально у меня есть состояние продукта, содержащее все продукты, полученные из API:
const [products, setProducts] = useState<Product[]>([]);
Я создал функцию для изменения информации о продукте. Несмотря на то, что я не обновлял состояние предыдущих продуктов, описание все равно меняется на новое:
const handleChangeEventProduct = async ({ productId }: Product) => {
const exitsProduct = products.find(({ id }) => id === productId);
exitsProduct.description = "example desc";
const response = await fetch(
`${process.env.VITE_APP_API_URL}/products/add`,
{
method: 'POST',
body: JSON.stringify(exitsProduct),
}
);
const responseObj = await response.json();
// Update the products state, I don't know why the products state is update the description to `example desc`.
if (responseObj?.success) {
setProducts(products);
}
}
С тех пор как я начал работать с React, я считаю этот метод правильным для обновления состояния.
const updatedProducts = products.map((product) => {
if (product.id === productId) {
return {
...product,
description: product.description,
}
}
return product;
});
setProducts(updatedProducts);
Если это связано с моим непониманием ReactJS, дайте мне знать. Можем ли мы обновить состояние нового продукта, даже если скопируем его в новую константу?
Я думаю, вы знаете, что показанный handleChangeEventProduct
нарушает фундаментальное правило состояния React: не изменяйте члены состояния напрямую. Я думаю, вы спрашиваете, почему вы видите обновленное описание, если функция делает это неправильно.
Это связано с тем, что React может повторно отрисовать ваш компонент по другим причинам, и при повторной отрисовке он будет использовать текущие свойства объектов в состоянии, включая свойства, которые были обновлены неправильно. Проблема с их неправильным обновлением заключается в том, что нет никакой гарантии того, когда и отобразит ли React внесенные вами изменения. Чтобы правильно убедиться, что React отображает изменения, вам нужно сделать то, что делает ваша функция updatedProducts
: создать новый массив и новый объект для продукта, который вы хотите обновить.
Вот пример. Выполните следующую команду и нажмите кнопку «Обновить только свойство». Обратите внимание: ничего не происходит. Но затем нажмите кнопку «Обновить свойство и счетчик» и обратите внимание, что и счетчик, и свойство отображаются повторно.
const { useState, useRef } = React;
const Example = () => {
const [counter, setCounter] = useState(0);
const [object, setObject] = useState({ property: 0 });
const update = (updateCounter) => {
++object.property; // <=== WRONG, don't do this
setObject(object); // <=== Does nothing
if (updateCounter) {
setCounter((c) => c + 1);
}
};
return (
<div>
<input
type = "button"
value = "Update Just Property"
onClick = {() => update(false)}
/>
<input
type = "button"
value = "Update Property and Counter"
onClick = {() => update(true)}
/>
<div>counter: {counter}</div>
<div>property: {object.property}</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id = "root"></div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Примечание: вызов setProducts(products);
handleChangeEventProduct
не обязательно что-либо сделает (и, по моему опыту, не делает), потому что products
(массив) не изменился (по крайней мере, в показанном коде), даже несмотря на то, что свойство один из объектов массива изменился.
@VietNguyen - Ну, нет никаких гарантий, что React не выполнит рендеринг, просто нет гарантий, что он это сделает (и я не могу это повторить - можете ли вы предоставить минимальный воспроизводимый пример?), React может решить для повторной визуализации вашего компонента в любое время. Но нет никаких сомнений в том, что handleChangeEventProduct
обновляет объект неправильно.
Это так странно, вот мой экран кода и красная линия, если я ее удалю, состояние никогда не обновится. Пожалуйста, посмотрите здесь ibb.co/g3GkfTk. Я уже пытался воспроизвести проблему здесь, но мне это не удалось.
@VietNguyen - Опять же, нет никаких гарантий, что React сделает с неправильным обновлением. Фундаментальным моментом здесь является то, что если вы хотите обновить продукт в этом массиве, ваш код в updatedProducts
— правильный способ сделать это. Если вы напрямую изменяете объект в состоянии, вы понятия не имеете, когда и будет ли это изменение отображено.
Я нашел ответ на этот вопрос, потому что я использую режим реагирования, и каким-то образом этот модальный режим вызывает изменение. Вы можете увидеть это здесь codeandbox.io/p/devbox/react-state-4nvrfr. На самом деле exitsProduct.description = "example desc";
никогда не обновляет состояние!
@VietNguyen - Это облегчение, мне действительно было интересно. :-) Хорошая находка!
Спасибо, но если я удалю
setProducts(products);
часть, описание не изменится!