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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:10:26