Search code examples
reactjstypescriptinputnannumeric

How to prevent non-numeric characters being put inside an input box


I am new to TypeScript and currently I am designing a BMR calculator. I have created a handleNumericInput function that only allows numbers to be accepted inside an input box but the problem is when I type a non-numeric character inside, it will either return NaN or clear the field entirely (depending on how I have set the value for each input). I just want it to not be typed at all. Here is my code:

App.tsx:

// Defined measurements and states
const [metricMeasurement, setMetricMeasurement] = useState<Metric>({
    heightInCentimetres: 0,
    weightInKilos: 0,
    age: 0,
  });

  // US measurement state
  const [usMeasurement, setUsMeasurement] = useState<US>({
    heightInFeet: 0,
    heightInInches: 0,
    weightInPounds: 0,
    age: 0
  });

// handleNumericInput function
const handleNumericInput = (e: React.ChangeEvent<HTMLInputElement>,
    measurementType: "metric" | "us"
  ) => {
    const { name, value } = e.target;
    if (/^\d*\.?\d*$/.test(value)) {
      measurementType === "metric" ? 
      setMetricMeasurement((prev) => ({ ...prev, [name]: Number(value) })) :
      setUsMeasurement((prev) => ({ ...prev, [name]: Number(value)}))
    } else {
      setMetricMeasurement((prev) => ({ ...prev, [name]: "" }))
      setUsMeasurement((prev) => ({ ...prev, [name]: "" }))
    }
  };

Metric.tsx:

<input 
  className='m-3 bg-slate-100 border w-full h-10 border-slate-300 rounded pl-1' 
  placeholder="cm"
  inputMode="numeric"
  type='text'
  pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
  value={metricMeasurement.heightInCentimetres || ''}
  name="heightInCentimetres"
  onChange={(e) => {
    setMetricMeasurement({
      ...metricMeasurement,
      heightInCentimetres: Number(e.target.value)
    });
    handleNumericInput(e, "metric");
  }}
/>

If i use value={metricMeasurement.heightInCentimetres || ''} and type a non-numeric character, it clears the field but if I just use value={metricMeasurement.heightInCentimetres} and type a non-numeric character, it returns NaN.

And yes, I am aware that you can use type="number" to only allow numbers to be typed. I have set to text to allow placeholders to define measurement types. Any help will be appreciated.


Solution

  • You're updating the state in two places. I think you should remove one from onChange and update it only if the input is numeric:

    <input 
      className='m-3 bg-slate-100 border w-full h-10 border-slate-300 rounded pl-1' 
      placeholder="cm"
      inputMode="numeric"
      type='text'
      pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
      value={metricMeasurement.heightInCentimetres || ''}
      name="heightInCentimetres"
      onChange={(e) => handleNumericInput(e, "metric")}
    />
    
    const handleNumericInput = (e: React.ChangeEvent<HTMLInputElement>,
        measurementType: "metric" | "us"
      ) => {
        const { name, value } = e.target;
        if (/^\d*\.?\d*$/.test(value)) {
          if (measurementType === "metric") { 
            setMetricMeasurement((prev) => ({ ...prev, [name]: Number(value) }))
          } else {
            setUsMeasurement((prev) => ({ ...prev, [name]: Number(value)}))
          }
        }
      };