javascript - React Native : [Unhandled promise rejection: Error: Invalid hook call. Hooks 只能在函数组件的主体内部调用

标签 javascript reactjs react-native

我是 React Native 的新手,这是我第一个使用 React Native 从头开始​​构建的项目。我有一个警告问题,我想要的功能不起作用。
以下是我想要的一些解释:
我有一个对象数组,我将其命名为购物车,在购物车内它看起来像:

let carts = [
  {
    merchantId: 1,
    items: [
      {
        productId: 1,
        productQty: 1,
      },
      {
        productId: 2,
        productQty: 5,
      },
    ],
  },
  {
    merchantId: 2,
    items: [
      {
        productId: 3,
        productQty: 2,
      },
      {
        productId: 4,
        productQty: 4,
      },
    ],
  },
];
这是我的屏幕:
import React, { useState, useEffect } from 'react';
import {
  StyleSheet,
  Text,
  View,
  Image,
  ScrollView,
  TouchableOpacity,
} from 'react-native';
import { Button } from 'react-native-paper';
import { theme } from '../../../infrastructure/theme';
import SpacerComponent from '../../../components/SpacerComponent';
import { FontAwesome5 } from '@expo/vector-icons';
import MerchantCardComponent from '../components/MerchantCardComponent';
import { tranformCartArray } from '../../../services/carts/CartTransform';

const CartScreen = ({ navigation }) => {
  const [carts, setCarts] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  const getCarts = async () => {
    setIsLoading(true);
    const response = await tranformCartArray();
    setCarts(response);
    setIsLoading(false);
  };

  useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      getCarts();
    });

    return unsubscribe;
  }, [navigation]);

  console.log(carts);

  if (carts.length === 0) {
    return (
      <View style={styles.emptyContainter}>
        <Image
          style={styles.imageCart}
          source={require('../../../assets/images/empty_cart.png')}
        />
        <Text style={styles.emptyCartText}>
          Keranjangnya masih kosong nih !
        </Text>
        <SpacerComponent size="xlarge" />
        <Button
          style={styles.buttonExplore}
          mode="contained"
          size={20}
          onPress={() => navigation.navigate('Merchants')}
        >
          <FontAwesome5
            name="shopping-cart"
            size={20}
            color={theme.colors.text.inverse}
          />
          <Text>{'   '}</Text>
          <Text style={styles.buttonText}>Eksplor</Text>
        </Button>
      </View>
    );
  }

  if (isLoading) {
    return (
      <View style={styles.emptyContainter}>
        <ActivityIndicator
          color={theme.colors.ui.primary}
          size={theme.sizes[3]}
        />
      </View>
    );
  }

  return (
    <ScrollView style={styles.container}>
      {carts.map((merchantAndItems) => {
        return (
          <TouchableOpacity onPress={() => navigation.navigate('Checkout')}>
            <MerchantCardComponent
              merchantAndItems={merchantAndItems}
              key={merchantAndItems.merchantId}
            />
          </TouchableOpacity>
        );
      })}
    </ScrollView>
  );
};

export default CartScreen;

const styles = StyleSheet.create({
  emptyContainter: {
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
    backgroundColor: theme.colors.bg.primary,
  },
  imageCart: {
    maxWidth: '90%',
    maxHeight: '45%',
    aspectRatio: 1,
  },
  emptyCartText: {
    fontSize: 20,
    fontFamily: theme.fonts.regular,
  },
  buttonExplore: {
    backgroundColor: theme.colors.ui.primary,
  },
  buttonText: {
    fontSize: 20,
  },
  container: {
    flex: 1,
    marginBottom: 10,
  },
});
我想在将数组的购物车转换为 MerchantCardComponent 后传递,我想要的转换类型是数组(购物车)变为:
let carts = [
  {
    merchantId: 1,
    merchantName: 'Merchant A',
    merchantLogo: 'https://assets.backend.com/merchants/1/logo_url.jpg',
    items: [
      {
        productId: 1,
        productName: 'Product One',
        productImage: 'https://assets.backend.com/merchants/1/products/1/product_image.jpg',
        productPrice: 2000,
        productQty: 1,
      },
      {
        productId: 2,
        productName: 'Product Two',
        productImage: 'https://assets.backend.com/merchants/1/products/2/product_image.jpg',
        productPrice: 2000,
        productQty: 5,
      },
    ],
  },
  {
    merchantId: 2,
    merchantName: 'Merchant B',
    merchantLogo: 'https://assets.backend.com/merchants/2/logo_url.jpg',
    items: [
      {
        productId: 3,
        productName: 'Product Three',
        productImage: 'https://assets.backend.com/merchants/2/products/3/product_image.jpg',
        productPrice: 2000,
        productQty: 2,
      },
      {
        productId: 4,
        productName: 'Product Four',
        productImage: 'https://assets.backend.com/merchants/4/products/4/product_image.jpg',
        productPrice: 2000,
        productQty: 4,
      },
    ],
  },
];
这样我就可以轻松地打印出文本,而无需在屏幕上再次获取后端。我已经为转换购物车功能制作了一个单独的文件,这里是代码:
import React, { useContext, useState } from 'react';
import CartsContext from './CartsContext';

import {
  doRequestMerchantDetail,
  doRequestProductDetail,
} from '../merchants/MerchantsService';

export const tranformCartArray = async () => {
  const [transformCart, setTransformCart] = useState([]);
  const [afterTransform, setAfterTransform] = useState(null);
  const [items, setItems] = useState([]);
  const { carts } = useContext(CartsContext);

  for (let i = 0; i < carts.length; i++) {
    let [errMerchant, merchant] = await doRequestMerchantDetail(
      carts[i].merchantId,
    );

    for (let j = 0; j < cart[i].items.length; j++) {
      let [errProduct, product] = await doRequestProductDetail(
        carts[i].items[j].productId,
      );
      if (product) {
        setItems(
          ...items,
          ...[
            {
              productName: product.product_name,
              productQty: carts[i].items[j].productQty,
              productPrice: product.product_price,
              productId: carts[i].items[j].productId,
              productImage: product.product_image_url,
            },
          ],
        );
      } else {
        setItems(
          ...items,
          ...[
            {
              productName: 'Error',
              productQty: carts[i].items[j].productQty,
              productPrice: 'Error',
              productId: carts[i].items[j].productId,
              productImage:
                'https://cdn.pixabay.com/photo/2021/07/21/12/49/error-6482984_960_720.png',
            },
          ],
        );
      }
    }

    if (merchant) {
      setAfterTransform({
        merchantName: merchant.name,
        merchantLogo: merchant.merchant_logo_url,
        merchantId: carts[i].merchantId,
        items,
      });
    } else {
      setAfterTransform({
        merchantName: 'Error',
        merchantLogo: 'Error',
        merchantId: carts[i].merchantId,
        items,
      });
    }

    setTransformCart(...transformCart, ...[afterTransform]);
  }

  return transformCart;
};
直到现在我仍然混淆是什么让警告显示出来,我在第一句话中提到的不工作是它在转换后返回一个空数组。这是控制台的屏幕截图
ConsoleLog in Screen

最佳答案

tranformCartArray不是 react 组件或自定义 react 钩子(Hook),所以你不能使用 useStateuseContext钩子(Hook)。
此外,抛出“未处理的拒绝”是因为 getCarts函数声明 async所以它隐式返回一个 Promise 并且当 React hooks 错误被抛出时没有 catch block 或 .catch一个 Promise 链来捕获和处理它。
我建议通过 carts从上下文状态进入tranformCartArray实用程序函数,并返回一个增强的购物车数组。这允许您从函数中删除任何和所有钩子(Hook)。

export const tranformCartArray = async (carts) => {
  const newCarts = [];

  for (let i = 0; i < carts.length; i++) {
    let [, merchant] = await doRequestMerchantDetail(
      carts[i].merchantId,
    );
    
    const newItems = [];

    for (let j = 0; j < carts[i].items.length; j++) {
      let [, product] = await doRequestProductDetail(
        carts[i].items[j].productId,
      );

      newItems.push({
        productName: product?.product_name ?? 'Error',
        productQty: carts[i].items[j].productQty,
        productPrice: product?.product_price ?? 'Error',
        productId: carts[i].items[j].productId,
        productImage: carts[i].items[j].product_image_url,
      });
    }
    
    newCarts.push({
      merchantName: merchant?.name ?? 'Error',
      merchantLogo: merchant?.merchant_logo_url ?? 'Error',
      merchantId: carts[i].merchantId,
      items: newItems,
    });
  }

  return newCarts;
};
购物车屏幕
const CartScreen = ({ navigation }) => {
  const [carts, setCarts] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  const { carts: contextCarts } = useContext(CartsContext); // <-- access context

  const getCarts = async () => {
    setIsLoading(true);
    try {
      const response = await tranformCartArray(contextCarts); // <-- pass context carts value
      setCarts(response);
    } catch(error) {
      // handle any errors?
    } finally {
      setIsLoading(false);
    }
  };

  ...

关于javascript - React Native : [Unhandled promise rejection: Error: Invalid hook call. Hooks 只能在函数组件的主体内部调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68697987/

相关文章:

javascript - 粘性侧边栏不会停止滚动

javascript - 简单表单无法在提交时关闭模式

javascript - 如何在 Joomla 2.5 文章中一起编写 HTML、CSS 和 Javascript?

javascript - 如何将 Prop 传递给 Material UI 类

reactjs - 在 useEffect 中 react 钩子(Hook) Prop

react-native - 将 Redux Store 连接到组件以使用标记填充 map

javascript - 错误: Bad Twitter streaming request: 401 using Twit

javascript - react, typescript - 无状态和普通组件的类型

react-native - 获取应用程序二进制文件未找到错误排毒测试

javascript - React Native Apollo 客户端 GraphQL Mutation 返回 400 错误