reactjs - React - 将上下文传递给 SweetAlert 弹出窗口

标签 reactjs react-router react-context sweetalert2

我的上下文如下:

import React, {createContext, useEffect, useState} from "react";

export const CartContext = createContext();

const CartContextProvider = (props) => {
    const [cart, setCart] = useState(JSON.parse(localStorage.getItem('cart')) || []);

    useEffect(() => {
        localStorage.setItem('cart', JSON.stringify(cart));
    }, [cart]);

    const updateCart = (productId, op) => {
        let updatedCart = [...cart];

        if (updatedCart.find(item => item.id === productId)) {
            let objIndex = updatedCart.findIndex((item => item.id === productId));

            if (op === '-' && updatedCart[objIndex].qty > 1) {
                updatedCart[objIndex].qty -= 1;
            } else if (op === '+') {
                updatedCart[objIndex].qty += 1;
            }
        } else {
            updatedCart.push({id: productId, qty: 1})
        }

        setCart(updatedCart);
    }

    const removeItem = (id) => {
        setCart(cart.filter(item => item.id !== id));
    };

    return (
        <CartContext.Provider value={{cart, updateCart, removeItem}}>
            {props.children}
        </CartContext.Provider>
    )
};

export default CartContextProvider;

App.js:

import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import NavigationBar from "./components/layout/navigationBar/NavigationBar";
import Homepage from "./pages/homepage/Homepage";
import AboutUsPage from "./pages/aboutUs/AboutUsPage";
import ContactPage from "./pages/contact/ContactPage";
import SearchPage from "./pages/search/SearchPage";
import ShoppingCart from "./components/layout/shoppingCart/ShoppingCart";
import CartContextProvider from "./context/CartContext";

function App() {
    return (
        <div>
            <CartContextProvider>
                <Router>
                    <NavigationBar/>
                    <ShoppingCart/>
                    <Routes>
                        <Route exact path="/" element={<Homepage/>}/>
                        <Route path="/a-propos" element={<AboutUsPage/>} />
                        <Route path="/contact" element={<ContactPage/>}/>
                        <Route path="/recherche" element={<SearchPage/>}/>
                    </Routes>
                </Router>
            </CartContextProvider>
        </div>
    );
}

export default App;

在组件 ShoppingCart 中,我使用了另一个组件 ShoppingCartQuantity,它又利用了上下文。它按预期工作。

这是 ShoppingCartQuantity 组件:

import React, {useContext} from "react";
import {CartContext} from "../../../context/CartContext";

import styles from './ShoppingCartQuantity.module.css'

const ShoppingCartQuantity = ({productId}) => {
    const {cart, updateCart} = useContext(CartContext);

    let qty = 0;
    if (cart.find((item => item.id === productId))) {
        let objIndex = cart.findIndex((item => item.id === productId));

        qty = cart[objIndex].qty;
    }

    return (
        <div>
            <span>
                <span className={`${styles.op} ${styles.decrementBtn}`} onClick={() => updateCart(productId, '-')}>-</span>
                <span className={styles.qty}>{qty}</span>
                <span className={`${styles.op} ${styles.incrementBtn}`} onClick={() => updateCart(productId, '+')}>+</span>
            </span>
        </div>
    )
}

export default ShoppingCartQuantity;

现在我尝试在 Homepage 组件中使用 ShoppingCartQuantity 组件,它是一个路由元素(引用 App.js)但是收到错误 Uncaught TypeError: Cannot destructure property 'cart' of '(0 , react__WEBPACK_IMPORTED_MODULE_0__.useContext)(...)' as it is undefined.

因此上下文适用于路由器外部的组件,但不适用于路由器内部的组件。如果我将路由器包装在提供者中,难道所有的路由元素都不能访问上下文,还是我遗漏了什么?

更新

正如用户 Build Though 在评论中建议的那样,我尝试在另一个路由元素中使用 ShoppingCartQuantity 组件,它工作正常;所以问题不在于路由器!

下面是我如何在 Homepage 组件中使用 ShoppingCartQuantity 组件的代码:

import React, { useState, useEffect,  useRef } from "react";
import { Responsive, WidthProvider } from "react-grid-layout";
import Subcat from "../../components/subcat/Subcat";
import CategoryService from "../../services/api/Category";
import SubCategoryService from "../../services/api/SubCategory";
import CategoriesLayout from "../../utils/CategoriesLayout";
import CategoryCard from "../../components/category/CategoryCard";
import { Triangle } from  'react-loader-spinner'
import ScrollIntoView from 'react-scroll-into-view'
import ProductService from "../../services/api/Product";
import Swal from 'sweetalert2'
import withReactContent from 'sweetalert2-react-content';
import YouTube from 'react-youtube';
import FavoriteBtn from "../../components/favorite/FavoriteBtn";
import ShoppingCartQuantity from "../../components/layout/shoppingCart/ShoppingCartQuantity";

import "./Homepage.css";
import "../../components/product/ProductModal.css"
import "react-loader-spinner";
import modalStyles from "../../components/product/ProductModal.module.css"

function Homepage() {
    const [categories, setCategories] = useState([]);
    const [subCats, setSubCats] = useState([]);
    const [loader, setLoader] = useState(false);
    const ResponsiveGridLayout = WidthProvider(Responsive);
    const scrollRef = useRef();
    const productModal = withReactContent(Swal);
    const opts = {
        // height: '390',
        // width: '640',
        playerVars: {
            autoplay: 1,
        }
    };

    useEffect(() => {
        CategoryService.get().then((response) => {
            setCategories(response);
        });
    }, []);

    function showSubCatsHandler(catId) {
        setLoader(true);
        setSubCats([]);
        SubCategoryService.get(catId).then((response) => {
            setSubCats(response.data);
            setLoader(false);
            scrollRef.current.scrollIntoView({ behavior: "smooth" });
        });
    }

    function showProductPopupHandler(productId) {
        ProductService.get(productId).then((response) => {
            const product = response.data;

            return productModal.fire({
                html:
                    <div>
                        <h3 className={modalStyles.header}>{product.AMP_Title}</h3>
                        <h4 className={`${modalStyles.price} ${modalStyles.header}`}>{"CHf " + product.AMP_Price}</h4>
                        <img className={modalStyles.image} src={process.env.REACT_APP_BACKEND_BASE_URL + 'images/products/' + product.AMP_Image} />
                        {
                            product.descriptions.map((desc, _) => (
                                <div key={desc.AMPD_GUID}>
                                    {
                                        desc.AMPD_Title === '1' && <h4 className={modalStyles.header}>{product.AMP_Title}</h4>
                                    }
                                    {
                                        desc.AMPD_Image !== '' && <img src={process.env.REACT_APP_BACKEND_BASE_URL + 'images/descriptions/' + desc.AMPD_Image} className={desc.AMPD_Alignment === 'left' ? modalStyles.descImageLeft : modalStyles.descImageRight} />
                                    }
                                    <p className={modalStyles.description}>{desc.AMPD_Description}</p>
                                </div>
                            ))
                        }
                        <br/>
                        <div>
                            <FavoriteBtn productId={product.AMP_GUID}/>
                            <ShoppingCartQuantity productId={product.AMP_GUID} />                          
                        </div>
                        <br/>
                        {
                            product.AMP_VideoId !== '' &&
                            <YouTube
                                videoId={product.AMP_VideoId}
                                opts={opts}
                            />
                        }
                    </div>,
                showConfirmButton: false,
                showCloseButton: true
            });
        });
    }

    return (
        <div>
            <div className="categories-container">
                <ResponsiveGridLayout
                    className="layout"
                    layouts={ CategoriesLayout }
                    breakpoints={ { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 } }
                    cols={ { lg: 8, md: 8, sm: 6, xs: 4, xxs: 2 } }
                    isDraggable={ false }
                >
                    {
                        categories.map((cat, index) => (
                            <div key={index}>
                                <CategoryCard
                                    category_id = {cat.AMC_GUID}
                                    image = {cat.AMC_Image}
                                    showSubCatsHandler = {showSubCatsHandler}
                                />
                            </div>
                        ))
                    }
                </ResponsiveGridLayout>
                {
                    loader &&
                    <Triangle
                        height="100"
                        width="100"
                        color='#bcad70'
                        ariaLabel='loading'
                        wrapperClass="loader"
                    />
                }
                <div ref={scrollRef}>
                    {
                        Object.keys(subCats).map((keyName, _) => (
                            <Subcat
                                key={subCats[keyName].AMSC_GUID}
                                title={ subCats[keyName].AMSC_Title }
                                products={ subCats[keyName].products }
                                showProductPopupHandler = {showProductPopupHandler}
                            />
                        ))
                    }
                </div>
            </div>
        </div>
    );
}

export default Homepage;

我在 SweetAlert 弹出窗口中使用该组件。我猜是 SweetAlert 组件没有访问上下文。有谁知道如何将上下文传递给 SweetAlert 组件?

更新 2

除 1 个小问题外,公认的解决方案效果很好:ShoppingCartQuantity 组件没有在 SweetAlert 弹出窗口和 qty 中重新呈现视觉上不会改变。

我通过使用 qty 作为 state 更新了组件。

const ShoppingCartQuantity = ({ qty, productId, updateCart }) => {
    const [quantity, setQuantity] = useState(qty);

    const updateCartHandler = (productId, amount) => {
        updateCart(productId, amount);
        setQuantity(Math.max(quantity + amount, 1));
    }

    return (
        <div>
            <span>
                <span
                    className={`${styles.op} ${styles.decrementBtn}`}
                    onClick={() => updateCartHandler(productId, -1)}
                >
                  -
                </span>
                <span className={styles.qty}>{quantity}</span>
                <span
                    className={`${styles.op} ${styles.incrementBtn}`}
                    onClick={() => updateCartHandler(productId, 1)}
                >
                  +
                </span>
            </span>
        </div>
    )
}

最佳答案

问题

甜蜜警报组件很可能在您的应用外部呈现,因此在 CartContextProvider 提供程序之外呈现。我只是搜索了 repo 文档是否有指定根元素的方法,但这似乎不可能,因为这个甜蜜的警报代码不是特定于 React 的。

查看其他类似的 issue关于访问警报中的 Redux 上下文。

解决方案

ATM 似乎无法从模式中访问上下文值,因此恕我直言,一种解决方法是将您的 ShoppingCartQuantity 组件重构为包装容器组件以访问上下文和演示文稿组件接收上下文值和任何回调。

我还建议将您想要增加/减少的数量传递给 updateCart 而不是传递 "+"/"-" 字符串和运算符比较。

例子:

export const withShoppingCartContext = Component => props => {
  const { cart, removeItem, updateCart } = useContext(CartContext);
  return <Component {...props} {...{ cart, removeItem, updateCart }} />;
}

const ShoppingCartQuantity = ({ cart, productId, updateCart }) => {
  const qty = cart.find(item => item.id === productId)?.qty ?? 0;

  return (
    <div>
      <span>
        <span
          className={`${styles.op} ${styles.decrementBtn}`}
          onClick={() => updateCart(productId, -1)}
        >
          -
        </span>
        <span className={styles.qty}>{qty}</span>
        <span
          className={`${styles.op} ${styles.incrementBtn}`}
          onClick={() => updateCart(productId, 1)}
        >
          +
        </span>
      </span>
    </div>
  )
}

export default ShoppingCartQuantity;

在应用中 CartContextProvider 中使用 ShoppingCartQuantity 组件的地方用 withShoppingCartContext HOC 装饰它并正常使用。

购物车

import ShoppingCartQuantityBase, {
  withShoppingCartContext
} from "../../components/layout/shoppingCart/ShoppingCartQuantity";

const ShoppingCartQuantity = withShoppingCartContext(ShoppingCartQuantityBase);

const ShoppingCart = (props) => {
  ...

  return (
    ...
    <ShoppingCartQuantity productId={....} />
    ...
  );
};

ShoppingCartQuantity 组件在上下文外部使用的地方,如在甜蜜模式中,访问 React 代码中的上下文并传入上下文值和回调。

...
import ShoppingCartQuantity from "../../components/layout/shoppingCart/ShoppingCartQuantity";
...

function Homepage() {
  ...
  const { cart, updateCart } = useContext(CartContext);
  const productModal = withReactContent(Swal);
  ...

  function showProductPopupHandler(productId) {
    ProductService.get(productId)
      .then((response) => {
        const product = response.data;

        return productModal.fire({
          html:
            <div>
              ...
              <div>
                <FavoriteBtn productId={product.AMP_GUID}/>
                <ShoppingCartQuantity
                  productId={product.AMP_GUID}
                  {...{ cart, updateCart }}
                />                          
              </div>
              ...
            </div>,
          showConfirmButton: false,
          showCloseButton: true
        });
      });
  }

  return (...);
}

export default Homepage;

其他问题

您的上下文提供程序在更新数量时正在改变状态。更新嵌套状态时,您仍应创建正在更新的数组元素的浅拷贝。

例子:

const CartContextProvider = (props) => {
  ...

  const updateCart = (productId, amount) => {
    // only update if item in cart
    if (cart.some(item => item.id === productId)) {
      // use functional state update to update from previous state
      // cart.map creates shallow copy of previous state
      setCart(cart => cart.map(item => item.id === productId
        ? {
          ...item, // copy item being updated into new object reference
          qty: Math.max(item.qty + amount, 1), // minimum quantity is 1
        }
        : item
      ));
    }
  }

  const removeItem = (id) => {
    setCart(cart => cart.filter(item => item.id !== id));
  };

  return (
    <CartContext.Provider value={{ cart, updateCart, removeItem }}>
      {props.children}
    </CartContext.Provider>
  );
};

关于reactjs - React - 将上下文传递给 SweetAlert 弹出窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72040860/

相关文章:

javascript - 警告 : Failed prop type: Invalid prop 'component' supplied to 'Route' - react-router-dom

reactjs - 我如何在 React 路由器 v6 中用上下文包装 2 个路由

android - 如何在 React Native 的抽屉导航中使用 ContextAPI?

javascript - fetch 原生支持多文件上传吗?

javascript - enzyme 3 获取呈现的 DOM 节点的属性

javascript - 类型错误 : Cannot read properties of undefined (reading 'preventDefault' ) React

javascript - 如何组织授权的react路线?

javascript - react 路由器 : catch a login-success url with a token being passed then redirect to homepage

javascript - 在不嵌套提供者的情况下 react 上下文?

javascript - React Redux - 当他们返回该页面时如何在特定位置使用