React Number Input

Handling native number input events as well

The number input

Number inputs are essentially natural when working with React applications, let’s learn how to handle it properly. There is a common mistake to fall when working with controlled number inputs:

//The ideal component, number inputs are equivalent to number states

const [value, setValue] = useState<number>(0);

return (
  <input
    type="number"
    value={value}
    onChange={(event) => setValue(+event.currentTarget.value)}
  />
);

❌ Wrong!! Number inputs needs number | null or number | "" states to handle empty inputs properly, if you restrict your state you will never clear your input.

The proposal

Do you remember to NaN value??, yessssss, thats the perfect solution! but, why?

console.log(typeof NaN); // "number", so...

const [value, setValue] = useState<number>(NaN);

return (
  <input
    type="number"
    // ✨ Magic
    value={isNaN(value) ? '' : value}
    // ✨ Magic
    onChange={(event) => setValue(event.currentTarget.valueAsNumber)}
  />
);

With this simple but powerful trick you will boost your code experience when working with number inputs in React, directly advantages:

// 1. Your declarations keeps pretty

type Person = {
  name: string;
  age: number; //you don't need to override this to handle empty inputs
};

const [person, setPerson] = useState({ name: '', age: NaN });

console.log(typeof person.age); // `age` always is a number type

return (
  <form>
    <input
      value={person.name}
      onChange={(event) =>
        setPerson((person) => ({ ...person, name: event.currentTarget.value }))
      }
    />

    <input
      type="number"
      value={isNaN(person.age) ? '' : person.age}
      onChange={(event) =>
        setPerson((person) => ({
          ...person,
          age: event.currentTarget.valueAsNumber,
        }))
      }
    />
  </form>
);
// 2. It's is backend/validation friendly

type Person = {
  name: string;
  age: number;
};

const person: Person = { name: 'Angel', age: NaN };

console.log(JSON.stringify(person)); // "{ "name": "Angel", "age": null }"

//null commonly mean an empty value

Conclusions

Trick 1, use NaN as number empty value

const [value, setValue] = useState<number>(NaN);

Trick 2, allow your inputs to be free

<input value={isNaN(value) ? '' : value} />

Trick 3, always use valueAsNumber

<input onChange={(event) => setValue(event.currentTarget.valueAsNumber)} />

Trick 4, consider NaN as null or undefined o "" value when validate

const isEmpty = (value: unknown) =>
  value === null || value === undefined || value === '' || value === NaN;

console.log(isEmpty(NaN)); //true
console.log(isEmpty('')); //true
console.log(isEmpty(null)); //true
console.log(isEmpty(undefined)); //true
console.log(isEmpty(12)); //false
console.log(isEmpty('Angel')); //false