React项目切换路由时频发报错求助(点击order链接触发)
问题
我开发了一个带侧边栏的React项目,侧边栏包含orders、users、products等路由链接。快速切换不同路由时会触发报错,虽然已经在路由变更时添加了loading状态重新加载,但点击order链接时问题尤为频发。我尝试在columns中添加可选链操作符仍未解决,当前报错来自products[0]相关代码,同时疑惑是否需要在路由集中管理的App.js中也添加loading状态?
相关代码
List.jsx
const List = ({columns}) => { const location = useLocation() const id = location.pathname.split('/')[1] const [loading,setLoading] = useState(true) const [data,setData] = useState([]) useEffect(() =>{ const getData = async () =>{ try { const res = await userRequest.get(`/${id}`) setData(res.data) setLoading(false) } catch (error) { } } getData() },[id, data, columns]) return ( <> {loading ? ( <BeatLoader color="#36d7b7" loading={loading} size={50} aria-label="Loading Spinner" data-testid="loader" /> ) : ( <div className="list"> <Sidebar/> <div className="listContainer"> <Navbar/> <Datatable columns={columns} data={data} setData={setData}/> </div> </div> )} </> ) } export default List
Datatable.jsx
const Datatable = ({data,setData, columns}) => { const [loading, setLoading] = useState(true) //const [data, setData] = useState(userRows); useEffect(() => { setLoading(false) },[data, setData, columns]) const handleDelete = (id) => { setData(data.filter((item) => item.id !== id)); }; const actionColumn = [ { field: "action", headerName: "Action", width: 200, renderCell: (params) => { return ( <div className="cellAction"> <Link to="/users/test" style={{ textDecoration: "none" }}> <div className="viewButton">View</div> </Link> <div className="deleteButton" onClick={() => handleDelete(params.row._id)} > Delete </div> </div> ); }, }, ]; return ( <div className="datatable"> <div className="datatableTitle"> <div className="search"> <input type="text" placeholder="Search..." /> <SearchOutlinedIcon /> </div> </div> {loading ? ( <BeatLoader color="#36d7b7" loading={loading} size={50} aria-label="Loading Spinner" data-testid="loader" /> ): ( <DataGrid className="datagrid" rows={data} getRowId={(row) => row._id} columns={columns.concat(actionColumn)} pageSize={9} rowsPerPageOptions={[9]} checkboxSelection /> )} </div> ); }; export default Datatable;
column.js(报错相关片段)
export const orderColumn = [ // ...其他配置 { field: "products", headerName: "Product", width: 150, renderCell: (params) =>{ return ( <div className="cellWithImg"> <img className="cellImg" src={params.row?.products[0]?.productId?.img} alt="avatar" /> {params.row?.products[0]?.productId?.title} </div> ) } }, // ...其他配置 ]
接口返回示例
{ "_id": "636357777877c919bb2cfa45", "userId": { "_id": "636355527877c919bb2cdf22", "studentId": "1234567891" }, "products": [ { "productId": { "_id": "636355a07877c919bb2cdfdc", "title": "Cabinet Update Shoe", "img": "https://firebasestorage.googleapis.com/v0/b/tua-ecom.appspot.com/o/1667454433424%5Bobject%20File%5D?alt=media&token=fb6cd8f1-a204-4a63-bcc2-7de2c93fd544" }, "quantity": 1, "sellerId": { "_id": "636355187877c919bb2cdf1f", "studentId": "1234567890" }, "_id": "636357777877c919bb2cfa46" } ], "amount": 100, "location": "Guard House", "time": "8:00", "status": "pending", "tax": 1, "createdAt": "2022-11-03T05:53:59.997Z", "updatedAt": "2022-11-03T05:53:59.997Z", "__v": 0 },
问题分析
- 竞态条件导致数据污染:快速切换路由时,前一次API请求可能未完成,新请求已发起。旧请求延迟返回后,会将不匹配当前路由的数据写入状态,此时渲染order列时,数据结构不匹配就会触发
products[0]相关报错。 - useEffect依赖错误:List组件的useEffect依赖了
data和columns,会导致数据更新时重复触发请求,加剧竞态问题;Datatable的loading逻辑直接在useEffect中置为false,未校验数据有效性。 - 可选链的局限性:虽然用了可选链,但如果
data是其他路由的数组(比如users列表),params.row根本没有products属性,或products为空数组,DataGrid的内部逻辑(如排序、过滤)仍可能触发读取错误。
解决方案
1. 取消旧请求解决竞态
在List组件中用AbortController取消未完成的请求,防止旧数据覆盖新数据:
const List = ({columns}) => { const location = useLocation() const id = location.pathname.split('/')[1] const [loading,setLoading] = useState(true) const [data,setData] = useState([]) useEffect(() =>{ const controller = new AbortController(); const signal = controller.signal; const getData = async () =>{ try { setLoading(true); // 切换路由立即置为loading const res = await userRequest.get(`/${id}`, { signal }); setData(res.data); } catch (error) { if (error.name !== 'AbortError') { console.error('请求失败:', error); } } finally { setLoading(false); } } getData(); // 路由变更或组件卸载时取消请求 return () => controller.abort(); },[id]) // 移除data、columns依赖,仅监听路由id变化 // 其余代码不变 }
2. 修正loading逻辑与依赖
- List组件的useEffect只需要监听
id变化,避免不必要的重复请求; - Datatable直接复用父组件的loading状态,避免重复维护:
// List组件传递loading <Datatable columns={columns} data={data} setData={setData} loading={loading}/> // Datatable组件接收并使用loading const Datatable = ({data,setData, columns, loading}) => { // 其余代码不变 }
3. 增强数据兜底校验
在orderColumn的renderCell中添加兜底逻辑,确保空数据时不会报错:
export const orderColumn = [ { field: "products", headerName: "Product", width: 150, renderCell: (params) =>{ const targetProduct = params.row?.products?.[0]?.productId; if (!targetProduct) { return <span>-</span>; // 空数据兜底显示 } return ( <div className="cellWithImg"> <img className="cellImg" src={targetProduct.img} alt="product" /> {targetProduct.title} </div> ) } }, // ...其他配置 ]
4. 路由层面loading(可选)
如果需要全局路由切换loading,可在App.js中用React Router的useNavigation实现:
// App.js import { useNavigation } from 'react-router-dom'; function App() { const navigation = useNavigation(); const isRouteLoading = navigation.state === 'loading'; return ( <> {isRouteLoading && <BeatLoader color="#36d7b7" size={50} />} <Routes> {/* 你的路由配置 */} </Routes> </> ); }
内容的提问来源于stack exchange,提问作者Stykgwar
相关产品推荐
相关产品推荐

