一不小心一腳踩進了數字輸入框的坑

orange plastic blocks on white surface
Photo by Jackson Sophat on Unsplash

今天在寫一個乍一看很簡單的功能時踩了個坑,記錄一下避免以後再踩。

input type=”number” 的巨坑

需求是有一個輸入框,這個輸入框只能輸入大於或等於 0 的數值,可以是整數或者小數。

最基礎的數字輸入框是這樣起步的:

import { useState } from "react"

export default function App() {
  const [value, setValue] = useState(0)

  const onChange = (e) => {
    setValue(e.target.value)
  }

  const onSubmit = (data) => {
    console.log(data)
  }

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="value">請輸入數字:</label>
      <input id="value" type="number" value={value} onChange={onChange} />
    </form>
  )
}

首先我給 input 加上了 type="number" 屬性,預期它只能輸入數值。

但實際上它能夠輸入所有內容,只不過下方會跳出一個「請輸入數字」的提示,但輸入的內容仍然保留在輸入框中。

image 42

甚至輸入小數也會提示「請選擇有效的值。最接近的有效值為0與1」

image 43

輸入框中的內容非數值時會回傳空字串

e.target.value log 出來,會發現從開始輸入非數字或 . 以外的內容時就沒有 log 出來:

image 39

只要輸入的內容包含除了數字,+,-,.,e以外的內容, value 的值和 input 框中的值就不會一致。

image 40
image 41

並且這還分兩種情況:

  1. 第一個輸入的值就非數值,那從一開始就不會觸發 onChange
  2. 輸入到一半時輸入了一個非數值的值,此時會觸發 onChange,但回傳的值會是空字串,並且在這之後輸入的任何內容都不會觸發 onChange

onKeyPress

在網上搜索解法的時候,看到滿多解法都是使用 onKeyPress,因為不會觸發 onChange 的話,可以改成監聽按壓的按鍵。不過 keypress event 目前已經被棄用了,因此不建議使用。

image 37
image 38

而且即使 onKeyPress 或者 onKeyDown 也可以藉由拼接字串的方式做到紀錄輸入的內容,但有點大費周章了。

用正則過濾輸入的內容

既然是因為 type="number" 而踩的坑,那不用不就行了(x

所以我改成用 type="text" 來實現,既然輸入什麼內容都可以,那就不會有 value 和輸入框中的值不同步的問題。只不過文字輸入框就不會自帶加減鍵了,得自己另外寫。

最開始有提到需求是只能輸入大於等於 0 的整數或小數,這可以用正則來將除了 0~9 和 . 以外的值替換成空字串:

const onChange = (value) => {
  value = value.replace(/[^0-9.]/g, "")
  setValue(value)
}

return (
    <form onSubmit={onSubmit}>
      <label htmlFor="value">請輸入數字:</label>
      <input
        id="value"
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
      <p>目前的值:{value}</p>
    </form>
  )

0開頭和多個小數點的處理

現在看起來是只能輸入數字和 . 了,但還有一些細節需要處理:

  1. 包含單個或多個 0 開頭的值,如:0012, 000001, 00.1
  2. 包含多個 . 的值,如:1..0, ...0, .0.0.

第一點可以用 /^0+/g 匹配沒意義的 0 並替換成空字串,不過要注意的是這樣輸入 0.x 會自動轉換為 .x,比如 0.6 會轉為 .6

第二點則是用 . 切割字串,如果長度大於 2 就代表有多個小數點,就要阻止最後一個 . 的輸入,直接 return。

const onChange = (value) => {
  if (value.split('.').length > 2) return
  value = value.replace(/[^0-9.]|^0+/g, "")
  setValue(value);
}

終於大致符合要求了,沒想到一個簡單的數字輸入框還有這麼多坑。

留言

目前沒有留言。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *