How I Outsmarted <input type="number" />: Crafting a Reusable React Component for Precision Medical Data


Building a Reliable Numeric Input for Medical Data in React
When developing an optometry prescription system, I encountered an issue that looked minor at first—but had real-world clinical implications. The built-in HTML number input field silently altered the data in a way that could compromise the accuracy of a prescription.
Here’s how I discovered the issue and built a customized solution using React and Material UI.
The Subtle Problem with Default Number Inputs
In standard forms, using an <input type="number" />
seems like the right choice for numeric values. But during testing with optometrists, I noticed something strange: when a user entered +1.50
, the input would strip out the plus sign and return 1.50
.
<input
type="number"
min="-20"
max="20"
step="0.25"
value={sphereValue}
onChange={(e) => setSphereValue(e.target.value)}
/>
From a general application perspective, this behavior might be fine. But in a clinical setting, the difference between +1.50
and 1.50
isn’t just visual—it can represent entirely different prescriptions.
This made it clear: I needed full control over how numeric input was captured, displayed, and validated.
Building a Custom Input with Character-Level Control
To address this, I built a custom numeric input component using Material UI’s TextField
. This component bypasses the limitations of native number inputs by treating numeric values as strings, giving me precise control over what’s accepted and how it’s processed.
Here’s the core implementation:
const NumericInput: React.FC<NumericInputProps> = ({
value,
onChange,
inputLabel,
errorCheck,
...rest
}) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const rawValue = e.target.value?.replace(/[^0-9+-.]/g, "") || "";
if (onChange) onChange(rawValue);
};
const getError = () => {
if (!value) return false;
return errorCheck ? errorCheck(value) : false;
};
return (
<TextField
inputProps={{
step: 0.25,
inputMode: "numeric",
...rest.inputProps,
}}
type="text"
value={value || ""}
onChange={handleChange}
error={getError()}
size="small"
label={inputLabel}
sx={{
"& .MuiInputBase-root": {
height: 32,
},
...rest.sx,
}}
InputLabelProps={{
shrink: true,
...rest.InputLabelProps,
}}
placeholder={inputLabel}
/>
);
};
Key features:
Allows explicit
+
and-
charactersFilters out invalid characters on input
Handles quarter-diopter precision (
0.25
,0.50
, etc.)Supports additional validation via
errorCheck
Validating the Component with Real-World Test Cases
Since this input could affect clinical outcomes, I wrote a test suite to ensure it behaves correctly in all expected use cases:
// Allows + and - symbols
it("accepts + and -", () => {
fireEvent.change(input, { target: { value: "+-123" } });
expect(onChange).toHaveBeenCalledWith("+-123");
});
// Allows decimal values
it("accepts quarter diopters", () => {
fireEvent.change(input, { target: { value: "-12.75" } });
expect(onChange).toHaveBeenCalledWith("-12.75");
});
// Strips invalid characters
it("removes letters/symbols", () => {
fireEvent.change(input, { target: { value: "abc!@#12.5d+" } });
expect(onChange).toHaveBeenCalledWith("12.5+");
});
// Handles null inputs gracefully
it("converts null to empty string", () => {
expect(input.value).toBe("");
});
This helped validate both the input sanitation and the flexibility of the component to handle realistic data entry scenarios.
Integrating with React Hook Form
The custom component works well with React Hook Form, making it easy to manage form state while retaining full control over validation and formatting.
<Controller
name="right_eye_dist_axis"
control={control}
render={({ field }) => (
<NumericInput
{...field}
inputLabel="AXIS"
sx={{ width: widthInput }}
/>
)}
/>
Lessons Learned
This experience reinforced a key principle: don’t rely on standard form components when precision is non-negotiable. In healthcare, even minor inconsistencies in data formatting can have outsized impacts.
What worked in this case:
Treating values as strings, not numbers
Filtering at the character level
Avoiding the default
<input type="number" />
when formatting matters
Final Thoughts
If you’re working on applications where data precision and formatting are clinically or operationally significant, consider building your form controls from the ground up. While it adds some complexity, it ensures you can handle all edge cases confidently—and responsibly.
Subscribe to my newsletter
Read articles from Lahiru Shiran directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
