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

MUI TextField搭配NumericFormat使用prefix时无法正常显示输入值

问题修复:MUI TextField + react-number-format 前缀异常问题

问题根源

  1. Ref类型错误:NumericFormatCustom的forwardRef泛型定义错误,导致MUI TextField无法正确关联输入框DOM元素,引发状态同步异常,表现为输入仅能输入一位、前缀消失。
  2. 数值校验逻辑缺陷:checkMinMax中使用parseInt处理可能含小数的数值,导致小数被截断,引发错误的最小/最大值校验。

修复后的完整代码

import { Observer } from "mobx-react-lite";
import { TextField, TextFieldProps } from "@mui/material";
import { AllowedNames } from "../utils/utils";
import { useDialogState } from "../states/dialog-state";
import { NumericFormat, NumericFormatProps } from "react-number-format";
import React from "react";

interface NumericFormatCustomProps {
  onChange: (event: { target: { name: string; value: string } }) => void;
  name: string;
}

// 修正Ref类型:第一个泛型为HTMLInputElement,对应输入框DOM元素的ref
const NumericFormatCustom = React.forwardRef<
  HTMLInputElement,
  NumericFormatCustomProps & NumericFormatProps<HTMLInputElement>
>(function NumericFormatCustom(props, ref) {
  const { onChange, prefix, suffix, ...other } = props;

  return (
    <NumericFormat
      {...other}
      getInputRef={ref}
      onValueChange={(values) => {
        onChange({
          target: {
            name: props.name,
            value: values.value, // values.value是不带格式的纯数字字符串,正确更新到状态
          },
        });
      }}
      prefix={prefix}
      suffix={suffix}
    />
  );
});

function getValue(value: any, coefficient: number) {
  if (value === "" || value === null || value === undefined) {
    return "";
  }
  return ((value ?? 0) / Math.pow(10, coefficient)).toString();
}

function setValue(value: any, coefficient: number, decimalPlaces: number) {
  if (value === "" || value === null || value === undefined) {
    return null;
  }
  return (
    (Math.round(parseFloat(value) * Math.pow(10, decimalPlaces)) /
      Math.pow(10, decimalPlaces)) *
    Math.pow(10, coefficient)
  );
}

export type FormNumberFieldProps<T extends object> = Omit<
  TextFieldProps,
  "defaultValue" | "type"
> & {
  dataKey: AllowedNames<T, number>;
  prefix?: string;
  suffix?: string;
  decimalPlaces?: number;
  coefficient?: number;
  thousandSeparator?: boolean;
  min?: number;
  max?: number;
};

export default function FormNumberField<T extends object>({
  dataKey,
  prefix = "$ ",
  suffix = "",
  decimalPlaces = 0,
  coefficient = 0,
  thousandSeparator = true,
  min,
  max,
  ...rest
}: FormNumberFieldProps<T>) {
  const [internalError, setInternalError] = React.useState("");
  const stateProvider = useDialogState<T>();
  const state = stateProvider.state;

  return (
    <Observer>
      {() => {
        const value = getValue(state.getValue(dataKey) as any, coefficient);

        const checkMinMax = () => {
          // 替换parseInt为parseFloat,支持小数数值的校验
          const numberValue = parseFloat(value);

          if (isNaN(numberValue)) {
            setInternalError("请输入有效的数字");
            return;
          }

          if (typeof min === "number" && numberValue < min) {
            setInternalError(`数值不能小于${min}`);
            return;
          }

          if (typeof max === "number" && numberValue > max) {
            setInternalError(`数值不能大于${max}`);
            return;
          }

          setInternalError("");
        };

        return (
          <TextField
            {...rest}
            fullWidth
            inputMode="numeric"
            onBlur={checkMinMax}
            value={value}
            onChange={(e) => {
              state.setValue(
                dataKey,
                setValue(e.target.value, coefficient, decimalPlaces) as any,
              );
            }}
            error={!!internalError || !!state.errorOf(dataKey)}
            helperText={internalError || state.errorOf(dataKey)}
            InputLabelProps={{ shrink: true }}
            InputProps={{
              inputComponent: NumericFormatCustom as any,
              inputProps: {
                prefix,
                suffix,
                thousandSeparator,
              },
            }}
          />
        );
      }}
    </Observer>
  );
}

关键修复点说明

  • Ref类型修正:将forwardRef的第一个泛型参数改为HTMLInputElement,确保MUI TextField能正确控制输入框DOM状态,解决输入同步问题。
  • 校验逻辑优化:用parseFloat替换parseInt,并增加isNaN判断,避免小数截断和非法输入导致的错误提示。
  • 状态同步保障:保留onValueChange中使用values.value(纯数字字符串)更新状态的逻辑,确保状态中始终存储原始数值,NumericFormat负责处理前缀、千分位等格式化显示。

内容的提问来源于stack exchange,提问作者Mümin Celal Pinar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 12:30:52