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

React页面刷新时API重复调用问题求助

解决API重复调用问题

我遇到了API重复调用的问题:每次刷新页面时,网络预览都会显示两次相同的API请求(地址为(ipaddress)/(portnumber)/admin/user-group?page=1&page_size=10&query=)。尝试修改useEffect、handleSearch方法均未解决,确定问题源于useEffect但找不到具体原因。

原始组件代码

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import SearchBar from './searchTable';
import Pagination from './paginationTable';

interface RowData {
  [key: string]: any;
}

interface DynamicTableProps {
  columns: string[];
}

const DynamicTable: React.FC<DynamicTableProps> = ({ columns }) => {
  const itemsPerPage = 10;
  const [currentPage, setCurrentPage] = useState(1);
  const [searchValue, setSearchValue] = useState('');
  const [filteredRows, setFilteredRows] = useState<RowData[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  interface UserGroupRow {
    id: number;
    name: string;
    email: string;
    phone: string;
    count: number;
  }

  const [totalItems, setTotalItems] = useState(0);

  const adminUrl = process.env.NEXT_PUBLIC_ADMIN_URL || '';

  const fetchData = async (query: string, page: number, pageSize: number) => {
    try {
      const token = localStorage.getItem('token');
      const headers = {
        'token': token ? token : '',
        'Content-Type': 'application/json',
      };
      setIsLoading(true);
      const response = await axios.get(`${adminUrl}/admin/user-group?page=${page}&page_size=${pageSize}&query=${query}`, { headers: headers });

      if (Array.isArray(response.data.data.user_groups)) {
        let formattedRows = response.data.data.user_groups.map((row: UserGroupRow) => {
          return {
            id: row.id,
            name: row.name,
            email: row.email,
            phone: row.phone,
            count: row.count,
          };
        });

        setFilteredRows(formattedRows);
        setTotalItems(response.data.meta.total);
      } else {
        setFilteredRows([]);
      }
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setIsLoading(false);
    }
  };

  const fetchDataWrapper = () => {
    if (searchValue === '') {
      fetchData('', currentPage, itemsPerPage);
    } else {
      fetchData(searchValue, currentPage, itemsPerPage);
    }
  };

  useEffect(() => {
    fetchDataWrapper();
  }, [searchValue, currentPage]);

  const handleSearch = (searchValue: string) => {
    setSearchValue(searchValue);
    setCurrentPage(1);
  };

  const handlePageChange = (newPage: number) => {
    setCurrentPage(newPage);
  };

  return (
    <div className="bg-white overflow-hidden shadow-md rounded-md">
      <div className="flex items-center justify-end px-6 py-3">
        <SearchBar onSearch={handleSearch} />
      </div>
      <table className="min-w-full divide-y divide-gray-200">
        <thead className="bg-gray-50">
          <tr>
            {columns.map((column) => (
              <th
                key={column}
                className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
              >
                {column}
              </th>
            ))}
          </tr>
        </thead>
        <tbody className="bg-white divide-y divide-gray-200">
          {filteredRows.map((row, index) => (
            <tr key={index}>
              {columns.map((column) => (
                <td key={column} className="px-6 py-4 whitespace-nowrap">
                  <div className="text-sm text-gray-900">
                    {row[column] ? row[column] : `No data for ${column}`}
                  </div>
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
      <Pagination
        currentPage={currentPage}
        totalItems={totalItems}
        itemsPerPage={itemsPerPage}
        onPageChange={handlePageChange}
      />
    </div>
  );
};

export default DynamicTable;

更新说明

为排查问题,我将数据请求逻辑抽离为自定义Hook,但问题依旧——无论如何修改useEffect,API都会被调用两次。

自定义Hook代码

import { useState, useEffect } from 'react';
import axios from 'axios';

interface FetchDataOptions {
  adminUrl: string;
  token: string | null;
  query: string;
  page: number;
  pageSize: number;
}

export const useFetchData = ({
  adminUrl,
  token,
  query,
  page,
  pageSize,
}: FetchDataOptions) => {
  const [data, setData] = useState<any[]>([]);
  const [totalItems, setTotalItems] = useState(0);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    if (!token) return;

    const fetchData = async () => {
      try {
        const headers = {
          token,
          'Content-Type': 'application/json',
        };
        setIsLoading(true);
        const response = await axios.get(
          `${adminUrl}/admin/user-group?page=${page}&page_size=${pageSize}&query=${query}`,
          { headers }
        );

        if (Array.isArray(response.data.data.user_groups)) {
          setData(response.data.data.user_groups);
          setTotalItems(response.data.meta.total);
        } else {
          setData([]);
        }
      } catch (error) {
        console.error('Error fetching data:', error);
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, [adminUrl, token, query, page, pageSize]);

  return { data, totalItems, isLoading };
};

解决方案

  • React Strict Mode 触发重复执行
    React 18的Strict Mode在开发环境下会自动执行两次组件挂载、useEffect等逻辑,用于检测潜在bug。这是开发环境的正常行为,生产环境不会出现该问题。

  • 依赖项变更触发多次请求
    检查useEffect依赖项:

    • 原始代码中useEffect依赖searchValue和currentPage,若初始化时这两个值发生多次变更(比如handleSearch无意中修改),会触发多次请求。
    • 自定义Hook中依赖adminUrl、token、query、page、pageSize,确认这些值是否在初始化时出现两次变化(比如token从null变为获取到的值)。
  • 具体修复措施

    1. 临时关闭Strict Mode测试:在入口文件(如index.tsx或_app.tsx)中移除<React.StrictMode>包裹,验证是否还会重复调用。但不建议长期关闭,它能帮助提前发现问题。
    2. 添加请求取消机制:使用AbortController取消未完成的请求,避免重复请求生效。修改自定义Hook示例:
      useEffect(() => {
        if (!token) return;
        const controller = new AbortController();
        const signal = controller.signal;
      
        const fetchData = async () => {
          try {
            const headers = {
              token,
              'Content-Type': 'application/json',
            };
            setIsLoading(true);
            const response = await axios.get(
              `${adminUrl}/admin/user-group?page=${page}&page_size=${pageSize}&query=${query}`,
              { headers, signal }
            );
      
            if (Array.isArray(response.data.data.user_groups)) {
              setData(response.data.data.user_groups);
              setTotalItems(response.data.meta.total);
            } else {
              setData([]);
            }
          } catch (error) {
            if (axios.isCancel(error)) return; // 忽略取消的请求
            console.error('Error fetching data:', error);
          } finally {
            setIsLoading(false);
          }
        };
      
        fetchData();
        return () => controller.abort(); // 组件卸载或依赖变化时取消请求
      }, [adminUrl, token, query, page, pageSize]);
      
    3. 确保依赖项稳定:确认adminUrl为静态值,token在组件初始化时一次性获取,避免频繁触发useEffect。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:54:56