更新localStorage时React Query的isLoading与isFetching异常问题
解决React Query删除购物车商品时触发isLoading的问题
问题背景
我正在为电商网站开发购物车页面,使用Redux进行状态管理、React Query进行数据管理,刚接触这两项技术。目前遇到了更新localStorage时的问题:Redux初始从localStorage加载购物车商品,但点击删除按钮移除商品时,React Query会触发isLoading,尝试使用isFetching也出现同样情况。
问题原因
- 查询键频繁变更:你将
cartItems.map(item => item.id)生成的ids数组作为useQuery的查询键一部分,删除商品时ids数组内容变化,React Query会判定为新查询,重新发起请求并触发isLoading。 - 冗余加载状态叠加:自定义的
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
相关产品推荐
相关产品推荐

