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

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
    },

问题分析
  1. 竞态条件导致数据污染:快速切换路由时,前一次API请求可能未完成,新请求已发起。旧请求延迟返回后,会将不匹配当前路由的数据写入状态,此时渲染order列时,数据结构不匹配就会触发products[0]相关报错。
  2. useEffect依赖错误:List组件的useEffect依赖了data和columns,会导致数据更新时重复触发请求,加剧竞态问题;Datatable的loading逻辑直接在useEffect中置为false,未校验数据有效性。
  3. 可选链的局限性:虽然用了可选链,但如果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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 15:45:46