React路由跳转产品详情页返回后选新卡片显示旧数据useEffect失效
问题背景
重构旧代码库时需要实现:每次选中卡片跳转至对应产品详情页时,展示最新更新的页面内容,而非上次浏览的旧页面。
异常现象
现有产品卡片列表页每次点击卡片时会调用history.push方法,携带对应卡片ID作为路径参数跳转到产品详情页,但从浏览器点击返回回到列表页后,选择其他卡片跳转时,详情页始终展示上一次访问的旧数据,而非当前选中卡片的最新内容。尝试使用useEffect钩子实现数据更新重置未达到预期,原业务实现代码如下:
const [ dataLoad, holdRender ] = useState(false); const [ product, setProduct ] = useState({}); const [ currentProduct, setCurrentProduct ] = useState(0); useEffect(()=> { (async () => { const productId = await props.getProduct(props.match.params.id); setCurrentProduct(productId); })(); return () => { if(!product){ setProduct({}) } } }, []); useEffect(() => { (async () => { const data = await props.security.product.data; setProduct(data); })(); //if product.details is now available if(product?.details){ //let go of render holdRender(true); } return () => { //tried to reset here if(currentProduct !== 0){ setCurrentProduct(0); } }; }, [currentProduct, product]); return( <div> {dataLoad !== false? <div> <Row> <Col> <Jumbotron id="productStatsCard"> <div className="productStats"> <h2>{product? product.details_not_verified : <Loader/>}</h2> </div> </Jumbotron> </Col> <Col> <Jumbotron id="productStatsCard"> <div className="productStats"> <h2>{product? product.details_verified: <Loader />}</h2> </div> </Jumbotron> </Col> </Row> <Row className="mt-5"> <Col sm={8}> <Row> <Col> <div className="pdtTableContainer"> <div> <Table> <thead> <tr> <th>Category</th> <th>Product Id</th> <th>Product Name</th> <th>Product Type</th> </tr> </thead> <tbody> {dataLoad && product?.details ? product?.details.map(detail => ( <tr key={product.id}> <td> {product.category} </td> <td>{product.id}</td> <td> {product.product_title} </td> <td>{product.product_type}</td> </tr> )) : 'No products to display'} </tbody> </Table> </div> </div> </Col> </Row> </Col> </Row> </div>: <Loader/>} </div> ) } const mapStateToProps = (state) => ({ security: state.security, }); export default connect(mapStateToProps, { getProduct })(productOverview);
根因分析
- 首屏数据拉取的
useEffect依赖数组为空,仅在组件首次挂载时执行。同路由参数变化时组件不会被卸载重挂载,导致路由ID更新后不会重新触发新产品数据的拉取逻辑。 - 多余的
currentProduct状态加错误的依赖设置:第二个useEffect将product加入依赖数组,每次setProduct触发状态更新都会重复执行副作用,形成无意义的重渲染循环;清理函数中重置currentProduct为0的逻辑会打断正常的数据拉取流程。 - 状态重置时序错误:路由参数变化时没有第一时间清空旧产品数据、重置加载状态,新数据拉取完成前页面会一直残留上一次访问的旧内容。
- 数据绑定逻辑错误:拉取产品数据时没有和当前路由ID做绑定校验,直接读取Redux中缓存的产品数据,很容易拿到上一次请求留存的旧值。
- 渲染逻辑存在冗余判断:
dataLoad !== false的判断没有实际意义,遍历详情列表时统一用product.id作为key会出现重复key问题,导致列表渲染异常。
修复方案
- 移除冗余的
currentProduct状态,将路由参数props.match.params.id加入数据拉取useEffect的依赖数组,路由ID变化时自动触发新的数据拉取流程。 - 副作用执行前先做状态重置:清空旧产品数据、将加载状态设为
false展示全局加载器,避免旧数据残留。 - 合并两个分散的
useEffect逻辑,等产品接口请求完成、Redux中对应产品数据更新后,再同步数据到组件状态、放开加载渲染。 - 删除错误的副作用清理逻辑,禁止在清理函数中重置当前流程依赖的状态值,避免打断正常请求。
- 修正渲染层逻辑问题:移除冗余的
dataLoad !== false判断,修复列表遍历的重复key问题,使用空值合并运算符替代原三目判断,避免合法falsy值被错误识别为未加载状态。
修复后的完整代码如下:
const [dataLoad, holdRender] = useState(false); const [product, setProduct] = useState({}); useEffect(() => { // 路由ID变化时立即重置状态,清空旧数据展示加载态 setProduct({}); holdRender(false); (async () => { const currentProductId = props.match.params.id; // 拉取当前路由对应ID的产品数据 await props.getProduct(currentProductId); // 数据拉取完成后同步Redux中的最新产品数据 setProduct(props.security.product.data); holdRender(true); })(); }, [props.match.params.id, props.security.product.data]); return( <div> {dataLoad ? <div> <Row> <Col> <Jumbotron id="productStatsCard"> <div className="productStats"> <h2>{product?.details_not_verified ?? <Loader/>}</h2> </div> </Jumbotron> </Col> <Col> <Jumbotron id="productStatsCard"> <div className="productStats"> <h2>{product?.details_verified ?? <Loader />}</h2> </div> </Jumbotron> </Col> </Row> <Row className="mt-5"> <Col sm={8}> <Row> <Col> <div className="pdtTableContainer"> <div> <Table> <thead> <tr> <th>Category</th> <th>Product Id</th> <th>Product Name</th> <th>Product Type</th> </tr> </thead> <tbody> {product?.details ? product.details.map((detail, index) => ( <tr key={detail.id ?? index}> <td>{product.category}</td> <td>{product.id}</td> <td>{product.product_title}</td> <td>{product.product_type}</td> </tr> )) : 'No products to display'} </tbody> </Table> </div> </div> </Col> </Row> </Col> </Row> </div>: <Loader/>} </div> ) } const mapStateToProps = (state) => ({ security: state.security, }); export default connect(mapStateToProps, { getProduct })(productOverview);
内容的提问来源于stack exchange,提问作者user8210845
相关产品推荐
相关产品推荐

