7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ReactAdvent Calendar 2021

Day 6

React Hook Formで数値を処理する際にハマったこと

Last updated at Posted at 2021-12-05

logo.png

本記事は、React Advent Calendar 2021の6日目の記事です。

Reactでフォームを作成するとき、簡単に作成できるReact Hook Formを使うことがよくあるかと思います。
本記事では、私がReact Hook Formで数値を処理する際にハマったこととその解決方法を紹介します。

なお、記事中のコードは一部分の引用です。記事の最後にCodeSandboxのリンクを記載しています。

はじめに

React Hook Formでは下記のようにuseForm()を使って必要な関数や値を取得します。
また、フォーム送信のハンドラ(ここではonSubmit)を定義して引数dataを与えることで、フォームに入力された値をdataで取得して、フォーム送信時の処理を記載することができます。

const App = () => {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => {
    // フォーム送信時の処理を記載する
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* ここにフォームを作る */}
      <input type="submit" />
    </form>
  );
};

TypeScriptを使う場合、useForm()に型を与えてdataの型を定義することができます。
この場合はIFormInputという型を定義しています。

interface IFormInput {
  firstName: string;
  lastName: string;
}

const App = () => {
  const { control, handleSubmit } = useForm<IFormInput>(); // 型を定義

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

さらに、React Hook FormはMUI(Material-UI)と組み合わせて使うこともできます。
ここでは、MUIのTextFieldと組み合わせて利用してみます。

作成したフォームはこちらです。
image.png

コードはこちらです。
下記のように、Controllerコンポーネントを用いることで、React Hook FormとMUIと組み合わせて使うことができます。

interface IFormInput {
  firstName: string;
  lastName: string;
}

const App = () => {
  const { control, handleSubmit } = useForm<IFormInput>();

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

  // Controllerコンポーネントを用いることでMaterial-UIと組み合わせて利用可能
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Stack spacing={2}>
        <label>First Name</label>
        <Controller
          render={({ field }) => <TextField {...field} />}
          name="firstName"
          defaultValue=""
          control={control}
        />
        <label>Last Name</label>
        <Controller
          render={({ field }) => <TextField {...field} />}
          name="lastName"
          defaultValue=""
          control={control}
        />
        <input type="submit" />
      </Stack>
    </form>
  );
};

より詳細な使い方は本記事では省略させていただきますので、公式ドキュメントなどをご参照ください。

発生した問題

今回、私はフォームで金額を入力したかったため、IFormInput numberpriceを追加しました。
また、priceをカンマ区切りで表示するためにtoLocaleString()を使いました。

type FormData = {
  firstName: string;
  lastName: string;
  price: number; // 追加
};

const App = () => {
  const { control, handleSubmit } = useForm<IFormInput>();

  const onSubmit = (data: IFormInput) => {
    console.log(data.price.toLocaleString()); // カンマ区切りで表示したい

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Stack spacing={2}>
        <label>First Name</label>
        <Controller
          render={({ field }) => <TextField {...field} />}
          name="firstName"
          defaultValue=""
          control={control}
        />
        <label>Last Name</label>
        <Controller
          render={({ field }) => <TextField {...field} />}
          name="lastName"
          defaultValue=""
          control={control}
        />
        {/* 追加 */}
        <label>Price</label>
        <Controller
          render={({ field }) => <TextField {...field} />}
          name="price"
          defaultValue=""
          control={control}
        />
        <input type="submit" />
      </Stack>
    </form>
  );
};

しかし、100000を入力してもコンソールには100000と出力され、うまく動きませんでした。(期待値は100,000
原因を調査したところ、typeofpriceの型を出力すると原因が分かりました。

interface IFormInput {
  firstName: string;
  lastName: string;
  price: number;
}

const App = () => {
  const { control, handleSubmit } = useForm<IFormInput>();

  const onSubmit = (data: IFormInput) => {
    console.log(typeof data.price); // string

なんとpricestringになっていました。
なので、toLocaleString()を使ってもカンマ区切りにはならなかったのです。

IFormInputnumberを指定してすっかり安心してしまっていました。
フォームからの出力なのでよく考えればstringしか定義できないような気はしますが、numberで指定して特にエラーにもならず、VSCodeの型表示もnumberとなっていたこともあり、ハマってしまいました。
異なる型でも代入できるのは、TypeScriptの型付けの闇な気がします。

修正版

pricestringにして、キャストして使うように修正しました。

interface IFormInput {
  firstName: string;
  lastName: string;
  price: string;
}

const App = () => {
  const { control, handleSubmit } = useForm<IFormInput>();

  const onSubmit = (data: IFormInput) => {
    console.log(Number(data.price).toLocaleString()); // カンマ区切りで表示される
  };

全体のコードはこちらをご確認ください。
数値以外を入力できないようにバリデーションを追加しています。

以上になります。
最後までお読みいただきありがとうございました。

7
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?