从子组件更新上下文时,我们可以触发父组件渲染吗?

Can we trigger parent component render when Context is updated from child component?

我有一个子组件,它是一个产品项。我在其中使用上下文来修改 json。当我修改项目的数量时(我现在使用 json 作为假数据库)。 在父组件中,我需要更新新的总数量、价格等。所以我在这个组件中订阅了 useEffect() 以进行数据更改,但它不会触发。我做错了什么?

cartAPIContext.js :

import { createContext, useEffect, useContext, useState, useMemo } from "react";


export const CartAPIContext = createContext();

const CartAPIContextProvider = ({ children }) => {
    const [data, setData] = useState([]);
    const [isLoading, setIsLoading] = useState(true);

    useEffect(() => {
        //On ira chercher le dernier panier actif de l'utilisateur
        let url = "/cartFakedb.json";

        fetch(url)
        .then(response => response.json())
        .then((data) => {
            setData(data);
            setIsLoading(false);
        })
        .catch((error) => console.log(error));
    }, [])

    const contextValue = useMemo(() => ({
        data,
        setData,
        isLoading
    }), [data, setData, isLoading])

    return (
        <CartAPIContext.Provider value={contextValue}>
            {children}
        </CartAPIContext.Provider>
    )
}


export default CartAPIContextProvider;

export function useCartAPI() {
    const context = useContext(CartAPIContext);
    if (context === undefined) {
        throw new Error("Context must be used within a Provider");
    }
    return context;
}

子组件:

import { forwardRef, useContext, useEffect, useState } from "react";
import { useCartAPI } from "./CartAPIContext";

const CartProductItem = ({ libraryId, product }) => {
    const { data, setData } = useCartAPI();

    const [ selectedItemQuantity, setSelectedItemQuantity ] = useState(product.quantity);

    const [ itemInStock, setItemInStock ] = useState(product.stock);
    const [ itemPrice, setItemPrice ] = useState((product.unitPrice*selectedItemQuantity).toFixed(2));

    function changeProductQuantity(event) {
        setSelectedItemQuantity(event.target.value);
    }

    useEffect(() => {
        for(let i = 0; i < data.libraries.length; i++) {
            if(data.libraries[i].id === libraryId) {
                for(let j = 0; j < data.libraries[i].products.length; j++) {
                    if(data.libraries[i].products[j].id === product.id) {
                        data.libraries[i].products[j].quantity = parseInt(selectedItemQuantity);
                    }
                }
            }
        }
        setData(data);
        setItemPrice((product.unitPrice*selectedItemQuantity).toFixed(2));
    }, [selectedItemQuantity]);

    console.log(data);

    return (
        <li className="item">
            <article>
                <div className="left">
                    <img src={product.cover_img} alt={product.title} loading="lazy" />
                    <select value={selectedItemQuantity} onChange={changeProductQuantity}>
                        {Array.from(Array(itemInStock)).map((empty, index) => 
                            <option key={product.id + index} value={index + 1}>{index + 1}</option>
                        )}
                    </select>
                </div>
            </article>
        </li>
    );
}

export default CartProductItem;

父组件:

import { useCartAPI } from "./CartAPIContext";
import CartProductList from "./CartProductList";
import { useEffect } from "react";

const CartLibraryItem = ({ library }) => {

    const { data, setData } = useCartAPI();

    function getTotalNumberOfArticles() {
        let total = {quantity: library.products[0].quantity};
        if (library.products.length > 1) {
            total = library.products.reduce((productA, productB) => {
                return { quantity: productA.quantity + productB.quantity };
            })
        }

        console.log(total);
        return total;
    }

    useEffect(() => {
        getTotalNumberOfArticles();
    }, [data]) //<---- I'm looking to trigger this useEffect(), when I use the select in the child component above.

    return (
        <li className="cart-library-item" key={library.id}>
            //I delete this part for space
        </li>
    );
}

export default CartLibraryItem;
data.libraries[i].products[j].quantity = parseInt(selectedItemQuantity);
//...
setData(data);

您正在改变状态,然后使用状态中已有的同一对象设置状态。 React 将旧状态与新状态进行比较,发现它们是同一个对象,因此它不会渲染。

您需要创建一个新状态。对于这样一个深度嵌套的对象,这有点痛苦,但它会是这样的:

useEffect(() => {
  const newData = {
    ...data,
    libraries: data.libraries.map(library => {
      if (library.id !== libraryId) {
        return library;
      }
      return {
        ...library,
        products: library.products.map(p => {
          if (p.id !== product.id) {
            return p;
          }
          return {
            ...p,
            quantity: parseInt(selectedItemQuantity)
          }
        });
      }
    })
  }
  setData(newData);
  setItemPrice((product.unitPrice*selectedItemQuantity).toFixed(2));
}, [selectedItemQuantity]);