You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native无限滚动触发Too many re-renders错误排查求助

React Native无限滚动「Too many re-renders」错误修复

问题描述

开发React Native无限滚动功能时触发错误:

Too many re-renders. React limits the number of renders to prevent an infinite loop.

错误定位在ReceivedBoxes.jsx的getReceivedBoxes函数(53:33),推测由间接修改状态导致,但无法准确定位问题。

错误原因分析

  1. onEndReached触发循环:FlatList的onEndReached直接传入getReceivedsBoxes函数,该函数内部会修改currentPage、loading等状态。每次状态更新触发组件重渲染,重渲染后FlatList会再次触发onEndReached;加上初始lastPage为undefined,currentPage > lastPage的判断不生效,导致无限调用getReceivedsBoxes。
  2. 函数未缓存:getReceivedsBoxes未用useCallback包裹,组件每次重渲染都会创建新的函数实例,FlatList的onEndReached属性频繁变化,加剧重复触发。
  3. 初始加载逻辑:两个useEffect分别调用数据获取函数,但未处理依赖项,可能导致不必要的重复执行。

修复步骤

  1. 用useCallback缓存getReceivedsBoxes,确保函数仅在依赖项变化时重新创建,避免FlatList重复触发onEndReached。
  2. 修正分页判断逻辑,处理lastPage为undefined的初始状态,避免无效请求。
  3. 调整useEffect依赖项,确保初始加载只执行一次,或在token变化时重新加载数据。
  4. 优化FlatList的onEndReached调用,确保仅在滚动到底部且非加载状态时触发请求。

修复后的完整代码

import { useCallback, useContext, useEffect, useState } from "react";
import { FlatList, ScrollView, TouchableOpacity, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Box, Center, Text } from "native-base";
import { useNavigation } from "@react-navigation/native";

import { authContext } from "../contexts/auth.context";
import { Api } from "../utils/api.util";

import { HeaderComponent } from "../components/Header";
import { ButtonComponent } from "../components/Button";
import { MemoizedTouchableReceivedBox } from "../components/TouchableReceivedBox";
import { MemoizedTouchableBoxReceivedInProcessing } from "../components/TouchableReceivedBoxInProcessing";

export function ReceivedBoxes() {
  const navigation = useNavigation();
  const { token } = useContext(authContext);

  const [receivedBoxes, setReceivedBoxes] = useState([]);
  const [rbInProcessing, setRBInProcessing] = useState([]);
  const [loading, setLoading] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const [lastPage, setLastPage] = useState(undefined);

  // 初始加载处理中数据
  useEffect(() => {
    getReceivedBoxesInProcessing();
  }, [token]);

  // 初始加载已接收数据
  useEffect(() => {
    if (token) {
      getReceivedsBoxes();
    }
  }, [token]);

  // 用useCallback缓存函数,避免重复创建
  const getReceivedsBoxes = useCallback(async () => {
    // 加载中或已到最后一页则返回
    if (loading || (lastPage !== undefined && currentPage > lastPage)) return;

    setLoading(true);

    try {
      const response = await Api(
        {
          route: `receiving?page=${currentPage}&image_size=small`,
          method: "GET",
        },
        token
      );

      const { success, receiving, pagination } = await response.json();

      if (success) {
        setReceivedBoxes((prev) => [...prev, ...receiving]);
        setLastPage(pagination.last_page);
        setCurrentPage(pagination.current_page + 1);
      }
    } catch (e) {
      console.warn(e);
    } finally {
      setLoading(false);
    }
  }, [loading, currentPage, lastPage, token]);

  async function getReceivedBoxesInProcessing() {
    try {
      const response = await Api(
        {
          route: `receiving/processing?page=1&image_size=small`,
          method: "GET",
        },
        token
      );

      const { success, receiving } = await response.json();

      if (success) {
        setRBInProcessing(receiving);
      }
    } catch (e) {
      console.warn(e);
    }
  }

  const renderItemReceivedBox = useCallback(({ item }) => (
    <MemoizedTouchableReceivedBox
      key={item.id} // 用item.id作为key更稳定,避免index变化导致的重渲染
      id={item.id}
      title={item.origin_tracking_number}
      qty={item.qty}
      weight_pound={item.weight_lbs}
      images={item.images}
      date={String(item.received_date)}
    />
  ), []);

  function EmptyListMessage() {
    return (
      <View style={{ alignItems: "center", justifyContent: "center" }}>
        <Text textAlign="center">Não há caixas recebidas a serem exibidas</Text>
      </View>
    );
  }

  return (
    <SafeAreaView style={{ backgroundColor: "#e6e6e6", height: "100%" }}>
      <HeaderComponent hasArrowBackButton={true} title="Caixas Recebidas" />

      <View style={{ marginHorizontal: 10, marginTop: 5 }}>
        <Center>
          <Box width="80%">
            <ButtonComponent
              buttonProps={{
                text: "Localize sua encomenda",
                isBold: true,
                action: () => {
                  navigation.navigate("LocateUrOrder");
                },
              }}
            />
          </Box>
        </Center>

        <Box marginBottom={6}>
          <Box
            marginTop={6}
            marginBottom={3}
            width="100%"
            flexDirection="row"
            justifyContent="space-between"
          >
            <Box>
              <Text fontSize={18} color="#15294B">
                Caixas em processamento
              </Text>
            </Box>

            <TouchableOpacity onPress={() => {}}>
              <Box backgroundColor="#FFF" paddingX={5} paddingY={1} borderRadius={15}>
                <Text fontSize={16} color="#894AFF">
                  Ver tudo
                </Text>
              </Box>
            </TouchableOpacity>
          </Box>

          <ScrollView horizontal>
            {rbInProcessing.map((receivedBox) => (
              <MemoizedTouchableBoxReceivedInProcessing
                key={receivedBox.id}
                id={receivedBox.id}
                title={receivedBox.origin_tracking_number}
                images={receivedBox.images}
                date={receivedBox.received_date}
              />
            ))}
          </ScrollView>
        </Box>

        <Box marginBottom={6}>
          <View style={{ height: "90%" }}>
            <FlatList
              data={receivedBoxes}
              renderItem={renderItemReceivedBox}
              keyExtractor={(item) => item.id.toString()} // 用item.id作为key更稳定
              onEndReachedThreshold={0.1}
              onEndReached={() => {
                if (!loading) getReceivedsBoxes();
              }}
              ListEmptyComponent={EmptyListMessage}
              contentContainerStyle={{ paddingBottom: 100 }}
            />
          </View>
        </Box>
      </View>
    </SafeAreaView>
  );
}

额外优化点

  • 将keyExtractor的key从index改为item.id,避免列表项顺序变化导致的不必要重渲染。
  • 移除了冗余的useMemo包裹renderItemReceivedBox,因为useCallback已经缓存了该函数。
  • 在onEndReached中增加了loading判断,确保同一时间只有一个请求在进行。

内容的提问来源于stack exchange,提问作者Alisson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 05:12:38