MUI TextField搭配NumericFormat使用prefix时无法正常显示输入值
问题修复:MUI TextField + react-number-format 前缀异常问题
问题根源
- Ref类型错误:
NumericFormatCustom的forwardRef泛型定义错误,导致MUI TextField无法正确关联输入框DOM元素,引发状态同步异常,表现为输入仅能输入一位、前缀消失。 - 数值校验逻辑缺陷:
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
相关产品推荐
相关产品推荐

