React分页状态丢失问题:从书籍详情返回后页码重置为1
分页页码状态丢失问题:返回详情页后回到第一页而非原页码
我开发了一个从后端获取书籍数据并实现分页功能的网站,分页功能本身运行正常,但存在一个问题:当我点击第二页的书籍进入详情页后,点击返回按钮会回到分页的第一页,而非之前的第二页。我需要实现分页当前页码状态的保存,确保返回时能回到原页码。
Shop组件代码
import React, { useEffect, useState } from 'react' import Footer from '../GlobalComponents/Footer' import Navbar from '../GlobalComponents/Navbar' import { RatingStar } from "rating-star"; import courses_bg from '../../Images/Banners/others_bg.jpg' import api from '../../AxiosInstance/api' import { useDispatch, useSelector } from 'react-redux' import { BookActions } from '../../Redux/Actions/Actions' import { Link } from 'react-router-dom'; const Shop = () => { const [pageNumber, setPageNumber] = useState(0) const [totalNumOfPages, setTotalNumOfPages] = useState(0) const dispatch = useDispatch() const AllShopBooks = useSelector(state => state.book.books) const pages = new Array(totalNumOfPages).fill(null).map((v, i) => i) useEffect(() => { const getBooks = async () => { try { const { data } = await api.get(`/book/get-books?page=${pageNumber}`) setTotalNumOfPages(data.totalPage) dispatch(BookActions(data.book)) } catch (error) { console.log(error) } } getBooks() }, [dispatch, pageNumber]) const handlePageNum = (pageIndex) => { setPageNumber(pageIndex) } return ( <div> <Navbar /> <section className='h-[50vh] lg:h-[90vh]' style={{ backgroundImage: `url(${courses_bg})`, backgroundPosition: 'center center ', backgroundSize: "cover", backgroundRepeat: "no-repeat" }}> <div className='flex justify-center items-center h-full'> <h1 className='text-white text-3xl lg:text-6xl '>Our Shop</h1> </div> </section> <section className='bg-[#f6f6f6] h-max py-20 px-10'> <div> <div className='grid mt-5 grid-cols-1 justify-items-center gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'> {AllShopBooks?.map((item, index) => ( <Link to={`/shop/${item._id}`} state={{ item }} className='mx-2 text-decoration-none' key={index} > <div className='w-max pr-4 bg-white py-4 '> <img src={item.image} alt='' /> <div className='mx-3 my-2 '> <h1 className='text-lg font-bold text-[#444444] !font-sans'>{item.title}</h1> <span className='text-sm '>Writer: <span className='font-bold ml-2'>{item.writer}</span> </span> <div className='flex items-center'> <span className='flex gap-2'> <span className='text-[#777777] font-semibold'>Price:</span> <span className='text-[#f6393e] font-bold text-base'>{item.price}</span> </span> <span><RatingStar id="different-maximum-score" rating={item.rating} maxScore={100} /></span> </div> </div> </div> </Link> ))} </div> </div> <div className="flex justify-center mt-9"> <ul className='flex cursor-pointer text-[#8a8a8a]'> {pages.map((pageIndex, index) => ( <li key={index} className={`px-4 py-2 border ${pageNumber === pageIndex ? "bg-[#cdcdcd] font-semibold" : ""}`} onClick={() => handlePageNum(pageIndex)}>{pageIndex + 1}</li> ))} </ul> </div> </section> <section> <Footer /> </section> </div> ) } export default Shop
BookDetails组件代码
import React, { useEffect, useState } from 'react' import Footer from '../GlobalComponents/Footer' import Navbar from '../GlobalComponents/Navbar' import courses_bg from '../../Images/Banners/others_bg.jpg' import { useLocation } from 'react-router-dom' import { RatingStar } from "rating-star"; import { AiOutlineMinus, AiOutlinePlus } from 'react-icons/ai' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' import { FeedBookActions } from '../../Redux/Actions/Actions' import api from '../../AxiosInstance/api' const BookDetails = () => { const navigate = useNavigate() const location = useLocation() const dispatch = useDispatch() const AllBooks = useSelector(state => state.book.feedBooks) const [count, setCount] = useState(0) const { title, writer, price, desc, rating, image } = location.state.item useEffect(() => { const getBooks = async () => { try { const { data } = await api.get("/book/feed-books") console.log(data) dispatch(FeedBookActions(data)) } catch (error) { console.log(error) } } getBooks() }, [dispatch]) const toDetailComponent = (title, item) => { navigate(`/book/${title}`, { state: { item } }) } return ( <div> <Navbar /> <section className='h-[50vh] lg:h-[90vh]' style={{ backgroundImage: `url(${courses_bg})`, backgroundPosition: 'center center ', backgroundSize: "cover", backgroundRepeat: "no-repeat" }}> <div className='flex justify-center items-center h-full'> <h1 className='text-white text-3xl lg:text-6xl '>Book Details</h1> </div> </section> <section> <div className='mx-10 flex flex-col lg:flex-row gap-5 py-20'> <div className='w-full md:h-[40vh] lg:h-[70vh] flex justify-center items-center bg-[#e6e6e6]'> <img className='text-center h-min' src={image} alt='' /> </div> <div className='w-full flex flex-col gap-2'> <h1 className='text-[#002147]'>{title}</h1> <div> <span className='text-justify text-[#8a8a8a]'>{desc}</span> </div> <div className='flex gap-3'> <span>Writer:</span> <span className='text-[#002147] font-semibold'>{writer}</span> </div> <div className='flex gap-3'> <span>Price:</span> <span className='text-[#002147] font-semibold'>{price}</span> </div> <div className='flex items-center'> <span>Rating:</span> <RatingStar id="different-maximum-score" rating={rating} maxScore={100} /> </div> <div className='flex lg:items-center lg:gap-5 flex-col lg:flex-row '> <div className='flex gap-2 h-min items-center'> <span className='bg-[#f0f0f0] py-2 px-3 text-xl cursor-pointer' onClick={() => setCount(count - 1)}><AiOutlineMinus /></span> <span className='bg-[#ffffff] py-2 px-3 text-xl font-semibold'>{ count}</span> <span className='bg-[#f0f0f0] py-2 px-3 text-xl cursor-pointer' onClick={() => setCount(count + 1)}><AiOutlinePlus /></span> </div> <div> <button className='my-4 w-36 px-4 py-2 bg-[#002147] text-[#fdc800] font-semibold'>Add to Cart</button> </div> </div> </div> </div> </section> <section className='bg-[#f6f6f6] py-20 px-10'> <div className='flex flex-col justify-center items-center h-full'> <h1 className='text-[#002147] text-2xl lg:text-4xl '>Our Shop</h1> <p className='leading-8 text-center'>Belis nisl adipiscing sapien sed malesu diame lacus eget erat Cras mollis scelerisqu <br></br>Nullam arcu liquam here was consequat.</p> </div> <div className='grid mt-5 grid-cols-1 justify-items-center gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'> {AllBooks?.map((item, index) => ( <div className='mx-2' key={index} onClick={() => toDetailComponent(item.title, item)}> <div className='w-max pr-4 bg-white py-4 '> <img src={item.image} alt='' /> <div className='mx-3 my-2 '> <h1 className='text-lg font-bold text-[#444444] !font-sans'>{item.title}</h1> <span className='text-sm '>Writer: <span className='font-bold ml-2'>{item.writer}</span> </span> <div className='flex items-center'> <span className='flex gap-2'> <span className='text-[#777777] font-semibold'>Price:</span> <span className='text-[#f6393e] font-bold text-base'>{item.price}</span> </span> <span><RatingStar id="different-maximum-score" rating={item.rating} maxScore={100} /></span> </div> </div> </div> </div> ))} </div> </section> <section> <Footer /> </section> </div> ) } export default BookDetails
解决方法
方案1:通过URL参数保存页码(推荐)
这种方式能同时支持返回恢复页码和页面刷新保留状态,符合URL语义化设计。
修改Shop组件:
- 引入
useSearchParams替代useState管理页码 - 切换页码时更新URL参数
- 监听URL参数变化触发数据请求
修改后的关键代码:
import { Link, useSearchParams } from 'react-router-dom'; const Shop = () => { // 从URL参数获取当前页码,默认0 const [searchParams, setSearchParams] = useSearchParams(); const pageNumber = parseInt(searchParams.get('page')) || 0; const [totalNumOfPages, setTotalNumOfPages] = useState(0) const dispatch = useDispatch() const AllShopBooks = useSelector(state => state.book.books) const pages = new Array(totalNumOfPages).fill(null).map((v, i) => i) useEffect(() => { const getBooks = async () => { try { const { data } = await api.get(`/book/get-books?page=${pageNumber}`) setTotalNumOfPages(data.totalPage) dispatch(BookActions(data.book)) } catch (error) { console.log(error) } } getBooks() }, [dispatch, pageNumber]) const handlePageNum = (pageIndex) => { // 更新URL的page参数 setSearchParams({ page: pageIndex }); } // 其余JSX代码保持不变 }
修改后,URL会显示/shop?page=1这样的格式,返回详情页后,URL参数依然存在,组件会自动加载对应页码的数据。
方案2:通过React Router State传递页码(仅返回有效)
如果不想修改URL,可以在跳转详情页时携带当前页码,返回时读取该状态恢复页码。
步骤1:修改Shop组件的Link跳转
<Link to={`/shop/${item._id}`} state={{ item, currentPage: pageNumber }} className='mx-2 text-decoration-none' key={index} >
步骤2:在BookDetails组件中返回时携带页码
// 在BookDetails中添加自定义返回逻辑 const goBack = () => { const currentPage = location.state?.currentPage || 0; navigate('/shop', { state: { prevPage: currentPage } }); } // 在页面中添加返回按钮,比如Navbar里的返回按钮调用goBack()
步骤3:在Shop组件中读取返回状态
import { useLocation } from 'react-router-dom'; const Shop = () => { const location = useLocation(); // 优先从返回状态取页码,否则默认0 const [pageNumber, setPageNumber] = useState(location.state?.prevPage || 0); // 其余代码保持不变 }
注意:这种方式在刷新Shop页面时,状态会丢失,回到第一页,因此仅适合不需要刷新保留状态的场景。
内容的提问来源于stack exchange,提问作者Saqib Raza
相关产品推荐
相关产品推荐

