React中Ref值未随输入更新致按钮禁用异常问题
解决React中Ref值不更新导致按钮Disabled状态不同步的问题
问题根源在于useRef的.current属性更新不会触发组件重渲染——哪怕输入框内容变了,组件也不会重新计算按钮的disabled判断条件,所以按钮状态一直没变化。下面给两种可行的解决方法:
方法一:用useState跟踪输入值(推荐)
改用state存储输入框的值,state变化会自动触发组件重渲染,这样disabled条件就能实时同步输入状态。
修改后的代码示例:
// imports import { useState } from 'react'; export default function Card() { // 用state存储所有输入框的值,结构匹配原ref的层级 const [inputValues, setInputValues] = useState({}); // 统一处理输入变化 const handleInputChange = (sectionId, exerciseName, value) => { setInputValues(prev => ({ ...prev, [sectionId]: { ...prev[sectionId], [exerciseName]: value } })); }; return ( // Rest of table <td className="flex-wrap px-2 text-sm text-gray-500"> {/* Weight input */} <input type="number" inputMode="decimal" className="w-12 pl-1 text-base rounded-sm bg-gravel-100" value={inputValues[section.id]?.[exercise.name] || ''} onChange={(e) => handleInputChange(section.id, exercise.name, e.target.value)} /> </td> <td> <button id={idx.toString()} disabled={ (loading && section.id + exercise.name === loadingExercise) || !inputValues[section.id]?.[exercise.name] || inputValues[section.id][exercise.name] === "" } onClick={() => { const inputValue = inputValues[section.id]?.[exercise.name]; const numValue = Number(inputValue); if (!isNaN(numValue)) { setWeightInDb(section.name, exercise.name, numValue); setLoadingExercise(section.id + exercise.name); // 清空输入框 handleInputChange(section.id, exercise.name, ''); } }} type="button" className="flex items-center justify-center w-8 h-8 border border-transparent rounded shadow-md disabled:shadow-none disabled:opacity-25" > {section.id + exercise.name === loadingExercise && loading ? ( <Spinner height={20} width={20} /> ) : ( <CheckIcon className="w-6 h-6 p-1 text-center text-nomad-700" /> )} </button> ); }
方法二:保留Ref但手动触发重渲染
如果一定要用ref存储输入元素,可以加一个专门用来触发重渲染的state(比如计数器),每次输入变化时更新这个计数器,强制组件重新渲染以读取最新的ref值。
代码示例:
// imports import { useState, useRef } from 'react'; export default function Card() { const fields = useRef<{ [key: string]: { [key: string]: HTMLInputElement } }>({}); // 用来触发重渲染的计数器state const [renderFlag, setRenderFlag] = useState(0); return ( // Rest of table <td className="flex-wrap px-2 text-sm text-gray-500"> {/* Weight input */} <input type="number" inputMode="decimal" className="w-12 pl-1 text-base rounded-sm bg-gravel-100" ref={(el) => { if (!fields.current[section.id]) fields.current[section.id] = {}; fields.current[section.id][exercise.name] = el as HTMLInputElement; }} onChange={() => { // 输入变化时更新计数器,触发重渲染 setRenderFlag(prev => prev + 1); }} /> </td> <td> <button id={idx.toString()} disabled={ (loading && section.id + exercise.name === loadingExercise) || !fields.current[section.id]?.[exercise.name] || fields.current[section.id][exercise.name].value === "" } onClick={() => { const inputEl = fields.current[section.id][exercise.name]; if (typeof inputEl.valueAsNumber === "number") { setWeightInDb(section.name, exercise.name, inputEl.valueAsNumber); setLoadingExercise(section.id + exercise.name); } inputEl.value = ""; // 清空后也要触发重渲染,更新按钮状态 setRenderFlag(prev => prev + 1); }} type="button" className="flex items-center justify-center w-8 h-8 border border-transparent rounded shadow-md disabled:shadow-none disabled:opacity-25" > {section.id + exercise.name === loadingExercise && loading ? ( <Spinner height={20} width={20} /> ) : ( <CheckIcon className="w-6 h-6 p-1 text-center text-nomad-700" /> )} </button> ); }
注意
方法一更符合React“state驱动UI”的设计理念,代码可读性和可维护性更高;方法二更适合必须保留ref的场景,但属于手动触发重渲染的hack手段,尽量少用。
内容的提问来源于stack exchange,提问作者Furkan Öztürk
相关产品推荐
相关产品推荐

