React商品页加载有效ID时误显404问题及解决方案咨询
问题根源
你遇到的核心问题是:组件初始状态下item为undefined,此时直接返回<NotFound />,但数据库查询是异步操作,请求完成前item一直是undefined,所以会先短暂显示404页面;等请求成功拿到数据后,item被赋值,才会渲染正常商品页。你之前加的延迟代码没有作用,因为wait(500)没有用await,不会阻塞代码执行,而且useEffect的依赖设置错误,导致逻辑重复触发。
解决方案
通过添加加载状态和明确的未找到状态,区分「正在加载」和「确实不存在」两种场景,同时修复异步请求的状态管理和useEffect依赖:
步骤1:添加状态变量
新增两个状态,分别标记加载状态和商品是否真的不存在:
const [isLoading, setIsLoading] = useState(true); const [isNotFound, setIsNotFound] = useState(false);
步骤2:修复getItem异步函数
修改请求逻辑,在请求开始时标记加载中,请求结束后(成功/失败)取消加载状态;当请求返回404时,标记商品不存在:
const getItem = async () => { setIsLoading(true); setIsNotFound(false); try { const res = await itemService.getItemById(parseInt(id)); setItem(res.data.product); } catch (error) { console.error(error); // 根据实际错误类型判断是否为404,比如检查error.response.status === 404 setIsNotFound(true); } finally { setIsLoading(false); } };
步骤3:调整useEffect依赖和逻辑
将useEffect的依赖改为id(只有商品ID变化时才重新请求),移除无用的延迟代码:
useEffect(() => { getItem(); setImages(imageMap[parseInt(id)] || []); // 加默认值避免空数组报错 getUserFromStorage(); }, [id]); // 依赖改为id,而非images
步骤4:调整渲染逻辑
根据加载状态和未找到状态,分别渲染对应内容:
// 加载中时显示加载组件(可自定义加载UI) if (isLoading) { return <div>Loading...</div>; } // 确实不存在时才返回404 if (isNotFound || !item) { return <NotFound />; } // 否则渲染商品页面 return ( // 原商品页面JSX... );
修改后的完整代码
const ItemPage: React.FC = () => { const [item, setItem] = useState<Item>(); const [quantity, setQuantity] = useState<number>(1); const [user, setUser] = useState<User>(); const id = window.location.pathname.split("/")[2]; const [images, setImages] = useState<string[]>([]); const [imageIndex, setImageIndex] = useState<number>(0); const [size, setSize] = useState<string>("Small"); const [authenticationMessage, setAuthenticationMessage] = useState<string>(""); const [popupMessage, setPopupMessage] = useState<string>(""); // 新增状态 const [isLoading, setIsLoading] = useState(true); const [isNotFound, setIsNotFound] = useState(false); const nav = useNavigate(); const imageMap: { [key: number]: string[] } = { 1: nascarImages, 2: heartlessImages, 3: anotherImages }; const cookies = new Cookies(); const jwt = cookies.get("jwttoken"); useEffect(() => { getItem(); setImages(imageMap[parseInt(id)] || []); getUserFromStorage(); }, [id]); // 依赖改为id const getItem = async () => { setIsLoading(true); setIsNotFound(false); try { const res = await itemService.getItemById(parseInt(id)); setItem(res.data.product); } catch (error) { console.error(error); // 根据实际错误类型判断是否为404 if (error instanceof Error && /* 补充你的404判断逻辑,比如error.response.status === 404 */) { setIsNotFound(true); } } finally { setIsLoading(false); } }; const getUserFromStorage = async () => { const userId = sessionStorage.getItem("id"); if (userId !== null) { try { const res = await userService.getUserById(parseInt(userId), jwt); setUser(res.data.user); } catch (error) { console.error(error); } } }; const nextImage = () => { setImageIndex(prev => prev === images.length - 1 ? 0 : prev + 1); }; const previousImage = () => { setImageIndex(prev => prev === 0 ? images.length - 1 : prev - 1); }; const addToCart = async () => { if (!user) { setAuthenticationMessage("You need to be logged in to add items to cart"); return; } if (!item) return; try { const res = await cartService.addToCart(user.id, item.id, quantity, size, jwt); if (res?.status === "success") { setPopupMessage("Item succesfully added to cart"); setTimeout(() => setPopupMessage(""), 3000); } else if (res?.status === "unauthorized") { setPopupMessage("It seems that you are not authenticated anymore, please login again"); setTimeout(() => setPopupMessage(""), 3000); } } catch (error) { console.error(error); } }; if (isLoading) { return <div>Loading...</div>; // 可替换为自定义加载组件 } if (isNotFound || !item) { return <NotFound />; } return ( <div className="itemContainer"> <div className="itemImages"> <button className="leftArrow" onClick={previousImage}></button> <img className="itemPicture" src={images[imageIndex]} alt="logo" /> <button className="rightArrow" onClick={nextImage}></button> </div> <div className="itemInfo"> <div className="itemTitle"> <h1 className="itemHeader">{item.name}</h1> </div> <div className={`popupMessage ${popupMessage.length === 0 ? '' : 'show'}`}> <p className="popupText">{popupMessage}</p> </div> <div> <p className="itemDescription">{item.description}</p> </div> <button className="toCartButton" onClick={addToCart}> Add to cart </button> {authenticationMessage && ( <p className="authenticationMessage">{authenticationMessage}</p> )} <div className="sizeContainer"> <button className="sizeButton" onClick={()=>setSize("Small")}>S</button> <button className="sizeButton" onClick={()=>setSize("Medium")}>M</button> <button className="sizeButton" onClick={()=>setSize("Large")}>L</button> </div> </div> </div> ); } export default ItemPage;
额外优化点
- 给
images设置默认空数组,避免imageIndex越界报错 - 简化
nextImage和previousImage的逻辑,使用函数式更新更可靠 - 给
getUserFromStorage添加错误处理,避免未捕获异常 - 在
addToCart里提前判断user和item,减少嵌套逻辑
内容的提问来源于stack exchange,提问作者david backx
相关产品推荐
相关产品推荐

