React useState изменяет все элементы массива в массиве объектов

#javascript #reactjs #react-hooks

Вопрос:

У меня есть 2 состояния продукта и вариации, которые я вызываю API и устанавливаю значения обоих состояний в ответе API.

Я хочу, чтобы состояние продукта оставалось таким, как оно есть, и не обновлялось

   const [product, setProduct] = useState({} as any);
  const [variations, setVariations] = useState([] as any);

  useEffect(() => {
    const getProduct = async () => {
      const data = await axios.get("/products?id=4533843820679");
      console.log(data);
      setProduct(data.data);
      // @ts-ignore
      setVariations([data.data]);
    };
    getProduct();
  }, []);
 

Взамен я сопоставляю массив вариантов и возвращаю входные данные для названия, цены и кнопки для добавления вариантов. Добавление вариаций добавит еще один продукт в массив вариаций. Так что это просто подталкивает продукт к вариациям.

Затем у меня есть входные данные для названия в вариациях и цены в вариациях.варианты. Проблема в onChange.

Когда я изменяю цену одного элемента в вариантах, она меняется для всех, а также изменяет ее для состояния ПРОДУКТА.

Код можно найти здесь: https://codesandbox.io/s/smoosh-firefly-6n747?file=/src/App.js

Добавьте варианты, измените цены добавьте еще варианты, и вы увидите все проблемы, с которыми я сталкиваюсь.

Комментарии:

1. В вашем примере CodeSandbox вы не копируете продукт глубоко, поэтому variants массив разделяется между объектами.

Ответ №1:

Именно из — за этого:

  variant.price = e.target.value; // same issue with title
 

variant ссылка на объект является общей для разных вариантов, и вы изменяете ее напрямую. Он является общим, потому что вы сделали неглубокую копию variation , используя ... его при добавлении.

Вот решение:

Вы должны обновить конкретный variant объект неизменяемым способом (в react вы всегда должны обновлять состояние неизменяемым способом). Для этого вам нужно использовать это как onChange для price :

 onChange = {
    (e) => {
        let updated = variations.map((x) => {
            if (x.id === variation.id) {
                return {
                    ...x,
                    variants: x.variants.map((y) => {
                        if (y.id === variant.id) {
                            return {
                                ...y,
                                price: e.target.value
                            };
                        }
                        return y;
                    })
                };
            }
            return x;
        });
        setVariations(updated);
    }
}
 

Это для onChange для title :

 onChange = {
    (e) => {
        let updated = variations.map((x) => {
            if (x.id === variation.id) {
                return {
                    ...x,
                    title: e.target.value
                };
            }
            return x;
        });
        setVariations(updated);
    }
}
 

ОБРАТИТЕ внимание, но идентификаторы вариантов должны быть разными. В целях тестирования вы можете использовать это в качестве обработчика щелчка при добавлении нового варианта:

 onClick = {
    () => {
        setVariations((prev) => [...prev, {
            ...product,
            id: Math.floor(Math.random() * 1000) // for testing
        }]);
    }
}
 

Комментарии:

1. Работает отлично! Спасибо!

Ответ №2:

Во-первых, вы не подталкиваете продукт к изменениям. Вы его переписываете.

Чтобы переместить значение в массив с помощью useState,

 setVariations([...variations, product])
 

Но если вы измените объект продукта, то также будут изменения, потому что это один и тот же объект. (Может быть, react не собирается перерисовывать его, но поверьте мне, он изменился.) Если вы хотите сохранить его прежним, вам нужно создать новый объект.

Так,

 setProduct(data.data);
setVariations([...variations, {...data.data}]);
 

Теперь вы можете изменить продукт. вариации не изменятся.

Ответ №3:

Это произошло потому, что вы сделали неглубокую копию объекта.

Попробуй сделать вот так:

 setVariations([...variations, data.data,]);