Next.js页面刷新时sortingWay状态重置问题求助
问题:Next.js动态路由页面刷新后排序状态丢失
我开发的Next.js应用包含动态路由products/[category]/[subcategory],这些页面复用带有排序功能的ProductsList组件,通过更新组件内的sortingWay状态实现页面间路由跳转。
根据Next.js官方文档:
在Next.js中导航至同一页面时,默认不会重置页面状态,因为React不会卸载组件,除非父组件发生变化。
当前同页面导航时状态正常,但当sortingWay有值时刷新页面,该状态会被重置。尝试通过localStorage保存状态但未生效。
相关代码
[category].js([subcategory].js与其基本一致)
import { useRouter } from 'next/router'; import ProdList from '../../../components/ProdList'; import React from "react"; export default function SubCategory({ products }) { const router = useRouter(); const category = router.query.category; const filtered = products.filter(product => category === product.category); return ( <> <h1>Товари підкатегорії {category}</h1> <ProdList data={filtered}/> </> ) } export async function getStaticProps() { const response = await fetch(`http://localhost:4000/products`); const data = await response.json(); return { props: { products: data } } } export async function getStaticPaths() { const response = await fetch('http://localhost:4000/products'); const data = await response.json(); const paths = data.map(product => { return { params: { category: product.category, }, } }) return { paths, fallback: false } }
ProductList.jsx组件
import React from 'react' import { useEffect, useState } from "react"; import { useRouter } from 'next/router'; import ProductItem from '../ProductItem'; import { productList } from './styles'; import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import FormControl from '@mui/material/FormControl'; import Select from '@mui/material/Select'; export default function ProdList({ data }) { const [ sortingWay, setSortingWay ] = useState(''); const [ productsList, setProductList ] = useState(data); const router = useRouter(); const { pathname, query } = router const category = router.query.category; const subCategory = router.query.subCategory; useEffect(() => { setSortingWay('') }, [router.query.subCategory]) useEffect(() => { setProductList(data) }, [router.query.category, router.query.subCategory]) useEffect(() => { addEventListener('beforeunload', event => { const data = window.localStorage.getItem('sorting___Way'); if(data.length > 0 ) setSortingWay(data) ; }) }, []); useEffect(() => { window.localStorage.setItem('sorting___Way', sortingWay); setSortingWay(sortingWay) }, [sortingWay]); // sorting const calcSalePrice = (price, sale) => { const normalized = parseInt(price.replace(/\s+/g, '')); return (normalized / 100) * (100 - sale); } const sortByPriceAscending = () => { return data.sort((a, b) => { const priceA = calcSalePrice(a.price, a.sale); const priceB = calcSalePrice(b.price, b.sale); return priceA - priceB; }) } const sortByPriceDescending = () => { return data.sort((a, b) => { const priceA = calcSalePrice(a.price, a.sale); const priceB = calcSalePrice(b.price, b.sale); return priceB - priceA; }) } const sortBySale = () => data.sort((a, b) => b.sale - a.sale); const sortByDefault = () => data.sort((a, b) => a.id - b.id); // https://nextjs.org/docs/routing/shallow-routing const shallowRoute = (targetValue) => { if (category && !subCategory) { return router.push(`/prod/${category}/?sortBy=${targetValue}`, undefined, { shallow: true }) } else if (category && subCategory) { return router.push(`/prod/${category}/${subCategory}/?sortBy=${targetValue}`, undefined, { shallow: true }) } } // handler const handleChange = ({ target }) => { const { value } = target; if (value === "asc") { setProductList(sortByPriceAscending()); } else if (value === "des") { setProductList(sortByPriceDescending()); } else if (value === "sale") { setProductList(sortBySale()); } else if (value === "default") { setProductList(sortByDefault()); } setSortingWay(value); shallowRoute(value) } return ( <div> <FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}> <InputLabel id="sort-products-label">Сортувати</InputLabel> <Select labelId="sort-products-label" id="sort-products" value={sortingWay} label="Сортувати" onChange={handleChange} > <MenuItem value="asc">За зростанням ціни</MenuItem> <MenuItem value="des">За спаданням ціни</MenuItem> <MenuItem value="sale">За знижками</MenuItem> <MenuItem value="default">За замовчуванням</MenuItem> </Select> </FormControl> <div> { productsList.map(product => <ProductItem key={product.id} id={product.id} subCategory={product.subCategory} title={product.title} price={product.price} image={product.image} sale={product.sale} /> ) } </div> </div> ) }
问题原因
localStorage读取时机错误:当前在beforeunload事件中读取localStorage,该事件仅在页面卸载前触发,刷新后组件重新挂载时未执行读取逻辑。- 冗余状态更新:监听
sortingWay的useEffect中重复调用setSortingWay(sortingWay),导致不必要的重渲染和状态混乱。 - 未利用URL参数同步状态:虽然通过shallow routing将排序参数写入URL,但刷新后未从URL的
sortBy参数读取状态,仅依赖localStorage存在可靠性问题(如隐私模式下存储被禁用)。 - 直接修改原始数据:排序函数使用
data.sort()原地修改传入的原始数组,违反React状态不可变原则,导致状态异常。
解决方案
1. 正确初始化sortingWay状态
组件挂载时优先从URL参数读取,其次读取localStorage,最后使用默认值:
const [sortingWay, setSortingWay] = useState(() => { // 服务端渲染时window不存在,需做判断 if (typeof window === 'undefined') return ''; // 优先取URL参数 const urlSort = router.query.sortBy; if (urlSort) return urlSort; // 其次取localStorage const storedSort = window.localStorage.getItem('sorting___Way'); return storedSort || ''; });
2. 修复localStorage读写逻辑
移除错误的beforeunload监听,仅在sortingWay变化时写入存储:
useEffect(() => { if (sortingWay && typeof window !== 'undefined') { window.localStorage.setItem('sorting___Way', sortingWay); } }, [sortingWay]);
3. 从URL参数同步状态
组件挂载或路由参数变化时,同步URL中的sortBy参数到状态:
useEffect(() => { if (router.query.sortBy && router.query.sortBy !== sortingWay) { setSortingWay(router.query.sortBy); // 同步执行排序 handleSort(router.query.sortBy); } }, [router.query.sortBy]);
4. 修复排序函数,避免修改原数据
使用扩展运算符复制数组后再排序,保证原始数据不被修改:
const sortByPriceAscending = () => { return [...data].sort((a, b) => { const priceA = calcSalePrice(a.price, a.sale); const priceB = calcSalePrice(b.price, b.sale); return priceA - priceB; }); }; const sortByPriceDescending = () => { return [...data].sort((a, b) => { const priceA = calcSalePrice(a.price, a.sale); const priceB = calcSalePrice(b.price, b.sale); return priceB - priceA; }); }; const sortBySale = () => [...data].sort((a, b) => b.sale - a.sale); const sortByDefault = () => [...data].sort((a, b) => a.id - b.id);
5. 优化状态更新逻辑
抽离排序逻辑为单独函数,简化handleChange:
const handleSort = (value) => { let sortedList = [...data]; switch(value) { case 'asc': sortedList = sortByPriceAscending(); break; case 'des': sortedList = sortByPriceDescending(); break; case 'sale': sortedList = sortBySale(); break; default: sortedList = sortByDefault(); } setProductList(sortedList); }; const handleChange = ({ target }) => { const { value } = target; setSortingWay(value); handleSort(value); shallowRoute(value); };
6. 清理不必要的状态重置
若切换子分类时不需要重置排序状态,移除以下代码;若需要重置,需同步更新URL参数和localStorage:
// useEffect(() => { // setSortingWay('') // }, [router.query.subCategory])
内容的提问来源于stack exchange,提问作者Катерина
相关产品推荐
相关产品推荐

