Next.js/React.js页面刷新后默认单选按钮消失问题求助
Next.js 13.4.8 App Router 单选按钮默认选中刷新后消失问题
在Next.js 13.4.8(React 18.2.0,App Router)环境中,通过next/link跳转至页面时,单选按钮默认选中功能正常;但页面刷新后,默认选中项会短暂显示后消失。已尝试状态惰性初始化、将默认值作为props传递等方案,问题仍未解决。
ShopItem 组件代码
"use client" import Image from 'next/image'; import React, { useState } from 'react'; function ShopItem({ item }) { // Find the default size object const defaultSize = item.sizes.find(sizeObj => sizeObj.default) || item.sizes[1]; // Fallback to first size if no default found // Initialize state to store the currently selected size and price const [selectedSize, setSelectedSize] = useState(defaultSize.size); const [selectedPrice, setSelectedPrice] = useState(defaultSize.price); const handleSizeChange = (size, price) => { setSelectedSize(size); setSelectedPrice(price); }; return ( <div className="shop__item"> <Image className="shop__item__img" src={item.image} width={500} height={500} alt={item.name} /> <div className="shop__item__content-wrapper"> <h2 className="shop__item__content shop__item__content__name"> {item.name} </h2> <div className="shop__item__content__weight-wrapper"> {item.sizes.map((sizeObj, index) => ( <React.Fragment key={index}> <input type="radio" id={`weight-${sizeObj.size}-${item.id}`} name={`weight-${item.id}`} value={sizeObj.size} onChange={() => handleSizeChange(sizeObj.size, sizeObj.price)} checked={sizeObj.size === selectedSize} className="visually-hidden" /> <label htmlFor={`weight-${sizeObj.size}-${item.id}`} className="button shop__item__content shop__item__content__weight">{sizeObj.size}</label> </React.Fragment> ))} </div> <div className="shop__item__content shop__item__content__price"> {selectedPrice} zł </div> <button className="button-big shop__item__content shop__item__content__add-to-cart"> Add to Cart </button> </div> </div> ); } export default ShopItem;
page.js 使用组件代码
import ShopItem from "./shop-item.js"; export default function Shop() { const items = [ { id: 1, name: "Miód rzepakowy", image: "/img/home/jak-rozpoznac.jpg", sizes: [ { size: "250 G", price: 59.9 }, { size: "450 G", price: 79.9, default: true }, { size: "1 KG", price: 99.9 } ], }, { id: 2, name: "Miód rzepakowy\nz nutką gryki", image: "/img/logo.png", sizes: [ { size: "250 G", price: 59.9 }, { size: "450 G", price: 79.9 }, { size: "1 KG", price: 99.9, default: true } ], }, ]; return ( <main className="page-wrapper main shop"> <div className="shop__1"> {items.map((item) => ( <ShopItem key={item.id} item={item} /> ))} </div> </main> ); }
已尝试的方案
- 状态惰性初始化:
const [selectedPrice, setSelectedPrice] = useState(() => defaultSize.price); - 将默认值作为props传递
解决方案
问题根源在于App Router的服务端渲染(SSR)+ 客户端 hydration流程:服务端渲染时无React状态,生成的DOM中单选按钮未被选中;客户端hydration时初始状态短暂生效,但组件重新渲染(如props更新)时,默认值计算的不一致会导致状态被重置。
修复代码
"use client" import Image from 'next/image'; import React, { useState, useEffect } from 'react'; function ShopItem({ item }) { const [selectedSize, setSelectedSize] = useState(''); const [selectedPrice, setSelectedPrice] = useState(0); // 监听item props变化,同步默认选中状态 useEffect(() => { const defaultSize = item.sizes.find(sizeObj => sizeObj.default) || item.sizes[0]; setSelectedSize(defaultSize.size); setSelectedPrice(defaultSize.price); }, [item]); const handleSizeChange = (size, price) => { setSelectedSize(size); setSelectedPrice(price); }; return ( <div className="shop__item"> <Image className="shop__item__img" src={item.image} width={500} height={500} alt={item.name} /> <div className="shop__item__content-wrapper"> <h2 className="shop__item__content shop__item__content__name"> {item.name} </h2> <div className="shop__item__content__weight-wrapper"> {item.sizes.map((sizeObj, index) => ( <React.Fragment key={index}> <input type="radio" id={`weight-${sizeObj.size}-${item.id}`} name={`weight-${item.id}`} value={sizeObj.size} onChange={() => handleSizeChange(sizeObj.size, sizeObj.price)} checked={sizeObj.size === selectedSize} className="visually-hidden" /> <label htmlFor={`weight-${sizeObj.size}-${item.id}`} className="button shop__item__content shop__item__content__weight">{sizeObj.size}</label> </React.Fragment> ))} </div> <div className="shop__item__content shop__item__content__price"> {selectedPrice} zł </div> <button className="button-big shop__item__content shop__item__content__add-to-cart"> Add to Cart </button> </div> </div> ); } export default ShopItem;
关键调整说明
- 修正fallback逻辑:将原代码中
item.sizes[1]改为item.sizes[0],避免数组索引越界问题 - 用useEffect同步props:监听
item变化,每次props更新时重新计算默认值并同步到状态,确保组件在SSR、hydration及后续渲染中都能保持正确的选中状态 - 可选:延迟初始化:若仍存在hydration不匹配问题,可在
useEffect中加入0延迟定时器,确保hydration完成后再设置状态:
useEffect(() => { const defaultSize = item.sizes.find(sizeObj => sizeObj.default) || item.sizes[0]; const timer = setTimeout(() => { setSelectedSize(defaultSize.size); setSelectedPrice(defaultSize.price); }, 0); return () => clearTimeout(timer); }, [item]);
内容的提问来源于stack exchange,提问作者Arden
相关产品推荐
相关产品推荐

