Dealing with number inputs in React can be a pain, especially when you need to make sure they're the right size or have the right number of decimal points. Simple stuff might seem easy, but once you get into the nitty-gritty and try to achieve custom user experiences, the code can get messy fast.
A common approach that most of us use is to write the custom validation logic that restricts the user input in onChange handler.
Something like this
function NumberInput({ value, onChange, min, max, }: { value: number; onChange: (val: number) => void; min?: number; max?: number; }) { const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { const val = +e.target.value; if (min != null && val < min) { onChange(min); return; } if (max != null && val > max) { onChange(max); return; } onChange(val); }; return ( <input type="number" value={value} onChange={changeHandler} /> ); }
This logic looks good at a first glance but if you have tried it already you know it comes with lot of unexpected and weird behaviours and not at all a good user experience !
Another approach is to use standard HTML element with the inbuilt validations using attributes like min, max, maxLength etc. However, it lacks the instant feedback and the input restrictions that we usually want to implement.
We end up Googling our way to Stack Overflow and find some... hackish solutions.
After a lot of trial and error, I finally figured out a better way to do this.
We can make use of inbuilt HTML input validation with some custom javascript to build a perfect solution.
Here is the component
// Interface for props overriding the default value and onChange // attribute to accept only numeric value export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> { onChange: (val: number) => void; value: number; } function NumberInput({ value, onChange, min, max, step, ...props }: NumberInputProps) { // Internal input state to avoid weird behaviors with empty inputs const [input, setInput] = React.useState(value?.toString() || ""); const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { // Using HTML input validity API to validation if ( (max != null && e.target.validity.rangeOverflow) || (min != null && e.target.validity.rangeUnderflow) || (step != null && e.target.validity.stepMismatch) ) return; const val = +e.target.value; setInput(e.target.value); onChange(val); }; // To ensure the external updates are reflected in the input element React.useEffect(() => { setInput(value.toString()); }, [value]); return ( <Input ref={ref} type="number" value={input} onChange={changeHandler} min={min} max={max} step={step} {...props} /> ); }
With this approach we can utilise the inbuilt HTML validations and also restrict the invalid user input for numbers.
Check out live example and play around
We can make this logic more reusable by extracting it out in a custom hook like this
export const useNumberInput = ({ value, onChange, min, max, step, }: { value: number; onChange: (val: number) => void; max?: number; min?: number; step?: number; }): InputHTMLAttributes<HTMLInputElement> => { const [input, setInput] = useState(value?.toString() || ""); const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { if ( (max != null && e.target.validity.rangeOverflow) || (min != null && e.target.validity.rangeUnderflow) || (step != null && e.target.validity.stepMismatch) ) return; const val = +e.target.value; setInput(e.target.value); onChange(val); }; useEffect(() => { setInput(value.toString()); }, [value]); return { type: "number", value: input, onChange: changeHandler, min, max, step, }; };
And use it in any component where required ( Obviously which has an input element ).
export default function CustomInput() { const [value, setValue] = useState(0); const inputProps = useNumberInput({ value, onChange: setValue, min: 1, max: 50, }); return ( <div> <button onClick={() => onChange(value + 1)}> + </button> <button onClick={() => onChange(value - 1)}> - </button> <input {...inputProps} {...otherProps} /> </div> ); }
Feel free to add comments and suggest improvements !
The above is the detailed content of Best way to handle number input validation in React. For more information, please follow other related articles on the PHP Chinese website!