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

更新localStorage时React Query的isLoading与isFetching异常问题

解决React Query删除购物车商品时触发isLoading的问题

问题背景

我正在为电商网站开发购物车页面,使用Redux进行状态管理、React Query进行数据管理,刚接触这两项技术。目前遇到了更新localStorage时的问题:Redux初始从localStorage加载购物车商品,但点击删除按钮移除商品时,React Query会触发isLoading,尝试使用isFetching也出现同样情况。

问题原因

  1. 查询键频繁变更:你将cartItems.map(item => item.id)生成的ids数组作为useQuery的查询键一部分,删除商品时ids数组内容变化,React Query会判定为新查询,重新发起请求并触发isLoading。
  2. 冗余加载状态叠加:自定义的loading状态与React Query的isLoading叠加,进一步加剧了加载态的触发频率。

解决方案

方案一:使用useQueries拆分单个商品查询

将批量查询拆分为单个商品的独立查询,删除操作只会影响对应商品的查询,不会触发整个购物车的重新加载:

import { useQueries } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
import CartProduct from './CartProduct';
import Link from 'next/link';

const CartPage = () => {
  const { cartItems } = useSelector((state) => state.cart);

  // 为每个购物车商品单独发起查询
  const productQueries = useQueries({
    queries: cartItems.map(item => ({
      queryKey: ['cartItem', item.id],
      queryFn: () => getCartItem(item.id), // 替换为单个商品的查询接口
      staleTime: Infinity,
      keepPreviousData: true // 保留旧数据直到新请求完成
    }))
  });

  // 判断是否有正在加载的查询
  const isLoading = productQueries.some(q => q.isLoading);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  // 提取成功获取到的商品数据
  const cartData = productQueries
    .filter(q => q.data)
    .map(q => q.data);

  return (
    <div className="container">
      {!cartItems.length ? (
        <div className="cart-empty">
          <h2 className="cart-empty__title">Your cart is empty</h2>
          <Link href="/shop" className="btn btn-primary">Go To Shop</Link>
        </div>
      ) : (
        <div className="cart-details-main-block" id="dynamic-cart">
          {cartData.map(item => (
            <CartProduct key={item._id} {...item} />
          ))}
        </div>
      )}
    </div>
  );
};

方案二:保留批量查询,启用keepPreviousData

如果必须使用批量查询接口getCartItems(ids),可以通过keepPreviousData选项让React Query在新请求完成前保留旧数据,避免触发isLoading:

const CartPage = () => {
  const { cartItems } = useSelector((state) => state.cart);
  const ids = cartItems.map(item => item.id);

  const {
    data: cartData,
    isLoading,
    isFetching
  } = useQuery(["cart", ids], () => getCartItems(ids), {
    staleTime: Infinity,
    keepPreviousData: true // 关键:保留旧数据直到新请求完成
  });

  // 仅在初始加载时显示加载态
  if (isLoading && !cartData) {
    return <p>Loading...</p>;
  }

  return (
    <div className="container">
      {!cartItems.length ? (
        <div className="cart-empty">
          <h2 className="cart-empty__title">Your cart is empty</h2>
          <Link href="/shop" className="btn btn-primary">Go To Shop</Link>
        </div>
      ) : (
        <div className="cart-details-main-block" id="dynamic-cart">
          {cartData?.map(item => (
            <CartProduct key={item._id} {...item} />
          ))}
        </div>
      )}
    </div>
  );
};

额外优化建议

  • 移除自定义loading状态:React Query的isLoading、isFetching已经能覆盖所有加载场景,无需额外维护useState的loading状态。
  • 用Mutation处理删除操作:如果删除商品需要同步到服务端,建议使用React Query的useMutation,删除成功后让对应查询失效,自动更新数据:
    const removeMutation = useMutation({
      mutationFn: (id) => deleteCartItem(id), // 服务端删除接口
      onSuccess: () => {
        // 失效对应商品的查询,触发重新获取
        queryClient.invalidateQueries(['cartItem', id]);
        // 或者失效整个购物车查询
        queryClient.invalidateQueries(['cart']);
      }
    });
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 19:37:07