Reactで数字をuseStateで追加する際によくある3つの間違い

ReactのuseStateでフォームの数字の扱いの間違い

ReactのuseStateでフォームの数字を変数に追加したいケースがよくあります。

しかし、Reactおよびフォームの仕様を理解していないと、意図しない結果になってしまうことがあります。

例えば、以下は <input type="number"> に数字を入力して計算結果を画面上に表示する処理なのですが、間違っている箇所が3つあります。

※ const [num, setNum] = useState<number | null>(null) の部分は正しいものとする

App.tsx
import { useState } from 'react'
import './App.css'

function App() {
  const [num, setNum] = useState<number | null>(null)

  return (
    <>
      <h1>React計算機</h1>
      <input
        type="number"
        value={num}
        onChange={(e) => {
          setNum(e.target.value)
        }}
      />
      <p>1 + {num} = {num ? 1 + num : ''}</p>
    </>
  )
}

export default App

こちらのコードを見て、どこが間違っているかわからなければ、Reactでコードを書く際には注意が必要です。

以降でどこが間違っているのか説明します。

間違い1: valueにnullが入っている

value={num} だと初期値のnullが入りますが、valueにnullを追加してはいけません。

ブラウザのConsoleを確認すると、valueに「空文字」を入れるようにとエラーが表示されてしまいます。

Reactでvalueにnullが入っている

valueがnullでも画面上の <input type="number"> には何も表示されないので、間違っていても気づかないことがよくあります。

nullのときは空文字を返すように value={num ?? ''} に変更してください。

App.tsx
import { useState } from 'react'
import './App.css'

function App() {
  const [num, setNum] = useState<number | null>(null)

  return (
    <>
      <h1>React計算機</h1>
      <input
        type="number"
        value={num ?? ''}
        onChange={(e) => {
          setNum(e.target.value)
        }}
      />
      <p>1 + {num} = {num ? 1 + num : ''}</p>
    </>
  )
}

export default App

ちなみに value={num || ''} だと 0 も空文字になってしまうので、このケースでは使用してはいけません。

間違い2: useState<number | null>でstringを代入

useState<number | null>なのにsetNum(e.target.value)で文字列を入れています。

フォームで入力された値は <input type="number"> だとしても、e.target.valueで取得するのは文字列の数字なので以下のようにNumberで数値に変換してからsetNumにセットする必要があります。

ただし、Number(e.target.value) にすると、空文字のときに Number('') が0になるため、問題があります。

空文字のときはnullにする必要があるので、setNum(value === '' ? null : Number(value)) にして追加します。

App.tsx
import { useState } from 'react'
import './App.css'

function App() {
  const [num, setNum] = useState<number | null>(null)

  return (
    <>
      <h1>React計算機</h1>
      <input
        type="number"
        value={num ?? ''}
        onChange={(e) => {
          const value = e.target.value
          setNum(value === '' ? null : Number(value))
        }}
      />
      <p>1 + {num} = {num ? 1 + num : ''}</p>
    </>
  )
}

export default App

間違い3: num ? 1 + num : '' で0ときの考慮漏れ

「num ? 1 + num : ''」だと num が「0」のときも空文字が返されてしまいます。

この場合は「num !== null ? 1 + num : ''」で判定することで正しい結果を返せます。

App.tsx
import { useState } from 'react'
import './App.css'

function App() {
  const [num, setNum] = useState<number | null>(null)

  return (
    <>
      <h1>React計算機</h1>
      <input
        type="number"
        value={num ?? ''}
        onChange={(e) => {
          const value = e.target.value
          setNum(value === '' ? null : Number(value))
        }}
      />
      <p>1 + {num} = {num !== null ? 1 + num : ''}</p>
    </>
  )
}

export default App

React計算機サンプル

ESLintでnullの考慮漏れを検出する

前述の「num ? 1 + num : ''」のような間違いはESLintのstrict-boolean-expressionsのルールを追加することで検出できます。

ESLintの推奨設定(eslint:recommended)にはstrict-boolean-expressionsは含まれていないため、必ずルールを追加することを推奨します。

eslint.config.js
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
  { ignores: ['dist', 'vite.config.ts'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parser: tseslint.parser,
      parserOptions: {
        project: './tsconfig.app.json',
        tsconfigRootDir: '.',
      },
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      '@typescript-eslint/strict-boolean-expressions': 'error',
    },
  }
)

strict-boolean-expressions | typescript-eslint