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变为获取到的值)。
- 原始代码中
具体修复措施
- 临时关闭Strict Mode测试:在入口文件(如
index.tsx或_app.tsx)中移除<React.StrictMode>包裹,验证是否还会重复调用。但不建议长期关闭,它能帮助提前发现问题。 - 添加请求取消机制:使用
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]); - 确保依赖项稳定:确认
adminUrl为静态值,token在组件初始化时一次性获取,避免频繁触发useEffect。
- 临时关闭Strict Mode测试:在入口文件(如
内容的提问来源于stack exchange,提问作者Ilyas Nawawie
相关产品推荐
相关产品推荐

