You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

关键调整说明

  1. 修正fallback逻辑:将原代码中item.sizes[1]改为item.sizes[0],避免数组索引越界问题
  2. 用useEffect同步props:监听item变化,每次props更新时重新计算默认值并同步到状态,确保组件在SSR、hydration及后续渲染中都能保持正确的选中状态
  3. 可选:延迟初始化:若仍存在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 03:10:01