React hookで気持ちよく入力チェック〜エラー判定を行う


突然ですがこういうフォームっていいですよね!

  • 入力が終わると、エラーかどうかを判定してエラー文を表示してくれる
  • 正しい入力に戻ると、エラー文が消える

gifにするとこういう感じ!

シンプルですが、「どういう風に書けばこうなるか」は案外知らないと作れないと思います

今回はこういうフォームの作り方をさくっとまとめます

要点をまとめると

  • onChangeで、「入力値」をuseStateへ保存する
  • onBlurで、「入力値を判定したエラー文」をuseStateへ保存する

これがわかればできます!

実装

では書いていきます

まずHTMLですが、「ラベル(はなくてもいいけど)」「入力欄」「エラー文を出す場所」があればいいです!

<div>
  <label>unique name</label>
  <input
    type="text"
    name="name"
    placeholder="Enter your name"
  />
  <p>error</p>
</div>

次にここへuseStateのロジックを混ぜていきましょう!

const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

return (
  <div>
    <label>unique name</label>
    <input
      type="text"
      name="name"
      placeholder="Enter your name"
      value={name}
      onChange={(e) => {
        setName(e.target.value)
      }}
    />
    {nameError && <p>{nameError}</p>}
  </div>
)

一気に増えましたが、大事なところだけ解説します!

まずuseStateで、必要なパラメーター「入力値」「入力値エラー」を作ります

const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

inputタグのvalueとonChange属性にnameとsetNameを渡すことで、useStateの値と、入力フォームの中身を一致させることができます

<input
  type="text"
  name="name"
  placeholder="Enter your name"
  value={name}
  onChange={(e) => {
    setName(e.target.value)
  }}
/>

次にエラー文。ちょっと慣れない書き方かもしれませんが、こういう風に書くと「nameErrorがfalseじゃない時」つまり、エラー文が存在する時だけ、うしろのpタグの中身を出してくれます

jsxならではの書き方ですね

{nameError && <p>{nameError}</p>}

最後の仕上げです!

onBlur属性の関数を定義して、入力フォームから離れた時に、入力値を判定して、エラー文を表示できるようにします

const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

const handleBlur = (e) => {
  const name = e.target.value
  if (!name) {
    setNameError('required')
  } else if (name.length < 5) {
    setNameError('should longer than 5')
  } else {
    setNameError()
  }
}

return (
  <div>
    <label>unique name</label>
    <input
      type="text"
      name="name"
      placeholder="Enter your name"
      value={name}
      onChange={(e) => {
        setName(e.target.value)
      }}
      onBlur={handleBlur}
    />
    {nameError && <p>{nameError}</p>}
  </div>
)

onBlurは他のところをクリックした時など、入力を中断した時だけ走ってくれるので、入力中には動作しません

また嬉しい点として、入力途中でボタンをクリックしても、ボタンのクリックイベントより先にonBlurが走ってくれるので、うっかり変なデータを送信することもありません!

ボタンも一緒に作る場合は、disabledを設定しておくと良いでしょう。(nameErrorが存在する時にdisabledがtrueになるようにする)

const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

const handleBlur = (e) => {
  const name = e.target.value
  if (!name) {
    setNameError('required')
  } else if (name.length < 5) {
    setNameError('should longer than 5')
  } else {
    setNameError()
  }
}

return (
  <div>
    <label>unique name</label>
    <input
      type="text"
      name="name"
      placeholder="Enter your name"
      value={name}
      onChange={(e) => {
        setName(e.target.value)
      }}
      onBlur={handleBlur}
    />
    {nameError && <p>{nameError}</p>}
  </div>
  <div>
    <button disabled={nameError}>Save</button>
  </div>
)

おまけ:入力値が空の時のe.target.value

...へは「空文字」が渡ってきます。

というより基本的にはe.target.valueは、数字を書いたとしても、基本全て文字列で渡ってきます(e.target.valueAsNumberというのもありますが、それはまた別の話ということで...)

つまり「文字列型」なので、今回のようなnameと行った文字列の入力値なら良いのですが、数字を扱いたい場合は都合が悪いです

なので、そういう場合はonChangeで仲介してあげましょう!

入力値が数字の場合の「空」をどう表現するかは人によると思うのですが、私はundefinedを使っています!

// 年齢のフォーム

<input
  type="number"
  name="age"
  placeholder="Enter your age"
  value={age}
  onChange={(e) => {
    if (e.target.value === '') {
      setAge(undefined)
    } else {
      // 入力値を数値へ変換!
      setAge(Number(e.target.value))
    }
  }}
/>

終わりに

今回はuseStateとinputのイベントハンドラを使って、エラー文を綺麗に出すフォームを作ってみました!

CSSやアニメーションをもっと付ければ、より洗練された感じになるので、是非やってみてください!