kk-web

input の value の型は 9 割 string 1 割 boolean

2021-04-06

よく勘違いしている人がいますが inputvalue は基本的に string しか取り得ることができません。

import React, { FC, useCallback } from "react";
import { useForm } from "react-hook-form";

type FieldValues = {
  hoge: string;
};

const App: FC = () => {
  const { register, handleSubmit } = useForm<FieldValues>();
  const onSubmit = useCallback(({ hoge }: FieldValues) => {
    // true
    console.log(typeof hoge === "string");
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("hoge")} />
      <button type="submit">submit</button>
    </form>
  );
};

export default App;

なぜ string しか取り得ることができないかというと、HTML ドキュメントに value を埋め込む以上、文字列しかしか扱うことができないからです。

たまに value の型を optional にしているケースを目にしますが、これは推奨される書き方ではありません。

import React, { FC, useCallback } from "react";
import { useForm } from "react-hook-form";

type FieldValues = {
  // 推奨されない書き方
  hoge?: string;
};

const App: FC = () => {
  const { register, handleSubmit } = useForm<FieldValues>({
    defaultValues: {
      // もし初期値に undefined を与えたとしても
      hoge: undefined,
    },
  });
  const onSubmit = useCallback(({ hoge }: FieldValues) => {
    // 型としては undefined を許容しているが value に undefined が格納されることはない
    // 何も入力されていない場合、空文字が格納される
    // true
    console.log(typeof hoge === "string");
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("hoge")} type="number" />
      <button type="submit">submit</button>
    </form>
  );
};

export default App;

当たり前といえば当たり前なんですが、HTML ドキュメントが文字列以外扱うことができない以上 undefinednull を扱うことはできません。

仮に typenumber を与えたとしても、基本的に value の型は string です。

import React, { FC, useCallback } from "react";
import { useForm } from "react-hook-form";

type FieldValues = {
  // 推奨されない書き方
  hoge: number;
};

const App: FC = () => {
  const { register, handleSubmit } = useForm<FieldValues>();
  const onSubmit = useCallback(({ hoge }: FieldValues) => {
    // 型としては number だが格納される値は string となってしまう
    // true
    console.log(typeof hoge === "string");
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* type に number を与えたとしても */}
      <input {...register("hoge")} type="number" />
      <button type="submit">submit</button>
    </form>
  );
};

export default App;

ただしややこしいのが初期値に number を与えた場合、初期値から値を変えなかった場合のみ valuenumber として扱われてしまいました。

import React, { FC, useCallback } from "react";
import { useForm } from "react-hook-form";

type FieldValues = {
  // 推奨されない書き方
  hoge: number;
};

const App: FC = () => {
  const { register, handleSubmit } = useForm<FieldValues>({
    defaultValues: {
      hoge: 0,
    },
  });
  const onSubmit = useCallback(({ hoge }: FieldValues) => {
    // value を初期値から変更していない場合のみ false
    // それ以外の場合 true
    console.log(typeof hoge === "string");
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("hoge")} type="number" />
      <button type="submit">submit</button>
    </form>
  );
};

export default App;

バグとも仕様ともなんとも言えない挙動ですが、value の型を string にしてやれば済む話なので気をつけましょう。

ただし、typecheckbox の場合のみ、value の型は boolean にしましょう。

import React, { FC, useCallback } from "react";
import { useForm } from "react-hook-form";

type FieldValues = {
  hoge: boolean;
};

const App: FC = () => {
  const { register, handleSubmit } = useForm<FieldValues>();
  const onSubmit = useCallback(({ hoge }: FieldValues) => {
    // true
    console.log(typeof hoge === "boolean");
  }, []);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("hoge")} type="checkbox" />
      <button type="submit">submit</button>
    </form>
  );
};

export default App;

checkbox の場合は checked か否かの 2 択であるため、パッケージ内でよしなに変換してくれているようです。

きちんと扱えていないケースをよく目にしてきたので、備忘録がてら。