Как правильно использовать useEffect с useContext в качестве зависимости

#reactjs #use-effect #react-functional-component #use-context

Вопрос:

Я работаю над своим первым проектом React, и у меня возникла следующая проблема. Как я хочу, чтобы мой код работал:

  • Я добавляю элементы в массив, доступный по контексту (context.items)
  • Я хочу запустить функцию useEffect в компоненте, где отображаются context.items при каждом изменении значения

Что я пытался:

  1. Перечисление контекста (обоих context и context.items ) в качестве зависимости в эффекте использования
  • это привело к тому, что компонент не обновлялся при изменении значений
  1. Перечисляя context.items.length
  • это привело к обновлению компонента, однако, при изменении длины массива, а не при изменении значений отдельных элементов.
  1. обертывание контекста в Object.values(context)
  • результат был именно тем, чего я хотел, за исключением того, что React теперь жалуется, что *Последний аргумент, переданный useEffect, изменил размер между рендерами. Порядок и размер этого массива должны оставаться постоянными. *

Знаете ли вы какой-либо способ исправить это предупреждение о реакции или другой способ запуска эффекта использования при изменении значения контекста?

Ну, я не хотел добавлять код, надеясь, что это будет какая-то простая ошибка с моей стороны, но даже с некоторыми ответами я все равно не смог это исправить, так что вот оно, уменьшенное в надежде на упрощение.

Компонент контекста:

 const NewOrder = createContext({
  orderItems: [{
    itemId: "",
    name: "",
    amount: 0,
    more:[""]
  }],
  addOrderItem: (newOItem: OrderItem) => {},
  removeOrderItem: (oItemId: string) => {},
  removeAllOrderItems: () => {},
});

export const NewOrderProvider: React.FC = (props) => {
  // state
  const [orderList, setOrderList] = useState<OrderItem[]>([]);

  const context = {
    orderItems: orderList,
    addOrderItem: addOItemHandler,
    removeOrderItem: removeOItemHandler,
    removeAllOrderItems: removeAllOItemsHandler,
  };

  // handlers
  function addOItemHandler(newOItem: OrderItem) {
    setOrderList((prevOrderList: OrderItem[]) => {
      prevOrderList.unshift(newOItem);
      return prevOrderList;
    });
  }
  function removeOItemHandler(oItemId: string) {
    setOrderList((prevOrderList: OrderItem[]) => {
      const itemToDeleteIndex = prevOrderList.findIndex((item: OrderItem) => item.itemId === oItemId);
      console.log(itemToDeleteIndex);
      prevOrderList.splice(itemToDeleteIndex, 1);
      return prevOrderList;
    });
  }
  function removeAllOItemsHandler() {
    setOrderList([]);
  }

  return <NewOrder.Provider value={context}>{props.children}</NewOrder.Provider>;
};

export default NewOrder;
 

the component (a modal actually) displaying the data:

 const OrderMenu: React.FC<{ isOpen: boolean; hideModal: Function }> = (
  props
) => {
const NewOrderContext = useContext(NewOrder);
useEffect(() => {
    if (NewOrderContext.orderItems.length > 0) {
      const oItems: JSX.Element[] = [];
      NewOrderContext.orderItems.forEach((item) => {
        const fullItem = {
          itemId:item.itemId,
          name: item.name,
          amount: item.amount,
          more: item.more,
        };
        oItems.push(
          <OItem item={fullItem} editItem={() => editItem(item.itemId)} key={item.itemId} />
        );
      });
      setContent(<div>{oItems}</div>);
    } else {
      exit();
    }
  }, [NewOrderContext.orderItems.length, props.isOpen]);
 

некоторые комментарии к коду:

  • на самом деле это делается в скрипте типа, который включает в себя некоторый дополнительный синтаксис-содержимое (и заданное содержимое)- это состояние, которое затем является частью возвращаемого значения, поэтому некоторые части могут быть установлены динамически-выход-это функция, закрывающая модальный, также почему props.is Открыто включено
  • с помощью этого .length расширения модальное отображение изменяется, когда я удаляю элемент из списка, однако не тогда,когда я изменяю его, не изменяя длину элементов заказа, а только значения одного из объектов внутри него.
  • как я уже упоминал ранее, я нашел несколько ответов, в которых говорится, что я должен установить зависимость следующим образом: ...Object.values(<contextVariable>) что технически работает, но приводит к жалобе react на то, что *Последний аргумент, переданный useEffect, изменил размер между рендерами. Порядок и размер этого массива должны оставаться постоянными. *
  • отображаемые значения изменяются на правильные значения, когда я закрываю и снова открываю модальный, изменение props.isOpen указывает на то, что проблема заключается в зависимости контекста

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

1. Предоставив этот фрагмент кода, я уверен, что вам больше повезет с получением помощи.

2. Если ссылка на элементы не изменилась, эффект использования больше не будет выполняться. Все зависит от того, как вы обновляете элементы (вы не должны изменять состояние).

3. Не могли бы вы, пожалуйста, показать нам, что содержит эффект использования и как вы обновляете состояние (контекст.элементы)?

4. @jperl вот что я считаю основной частью кода, в которой заключается моя проблема, надеюсь, я не пропустил ничего важного

5. Я не знаю, где вы нашли эти ответы, которые советуют вам использовать ...Object.values(<contextVariable>) , но, пожалуйста, не делайте этого, это ужасно. @EmmaJoe на самом деле привел вам хороший пример. Вы заметили разницу? Как я уже говорил вам, до тех пор, пока ссылка не изменится, эффект использования не будет повторяться. ЭммаДжо разрушил предыдущее значение, чтобы создать новое, вот так let updatedCart = [...cart] . Таким образом, мы получаем новую ссылку. Вы этого не сделали, вы взяли предыдущее значение и применили к нему unshift. Это плохо, вы не только изменили состояние, но и не получили новую ссылку.

Ответ №1:

Вы можете начать с создания контекста вашего приложения, как показано ниже, я буду использовать пример корзины покупок

 import * as React from "react"

const AppContext = React.createContext({
  cart:[]
});

const AppContextProvider = (props) => {
  const [cart,setCart] = React.useState([])

  const addCartItem = (newItem)=>{
    let updatedCart = [...cart];
    updatedCart.push(newItem)
    setCart(updatedCart)
    
  }
  
  return <AppContext.Provider value={{
    cart
  }}>{props.children}</AppContext.Provider>;
};

const useAppContext = () => React.useContext(AppContext);

export { AppContextProvider, useAppContext };
 

Затем вы используете контекст приложения в любом месте приложения, как показано ниже, всякий раз, когда длина корзины меняется, вы получаете уведомление в корзине покупок

 import * as React from "react";
import { useAppContext } from "../../context/app,context";

const ShoppingCart: React.FC = () => {
  const appContext = useAppContext();

  React.useEffect(() => {
    console.log(appContext.cart.length);
  }, [appContext.cart]);
  return <div>{appContext.cart.length}</div>;
};

export default ShoppingCart;
 

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

1. спасибо за ваш ответ, хотя я сначала не понял, почему, это правильное решение.

2. Для таких, как я, кто все еще не понимает этого после просмотра этого кода, основная часть объясняется в комментариях под вопросом.

Ответ №2:

Вы можете попробовать передать контекстную переменную в массив зависимостей useEffect, а внутри тела useEffect выполнить проверку, например, чтобы убедиться, что значение не равно нулю.