🐕

ReactのフォームのState管理を再度学習してみる

2024/04/09に公開

FormでState管理ってめんどくさ~い

そもそもですが、私はReact、NextJsは初学者なのでuseStateを使うのがReactの醍醐味でなんでもかんでもuseStateを使う!っていうのが今までだった。。。
ただほかに絶対いい方法があるだろうということで海外のYoutuberの動画を見たらなにやら今までなじみのないものに出会ったわけだ

const [editingEmployee, setEditingEmployee] = useState({
    _id: "",
    name: "",
    birthDate: "",
    basicSalary: 0,
    jobAllowance: 0,
    housingAllowance: 0,
    commutingAllowance: 0,
    paidLeaveRemaining: 0,
  });
 const { name, value } = e.target;
    setEmployeeInfo((prevState) => ({
      ...prevState,
      [name]:
        name === "basicSalary" ||
        name === "jobAllowance" ||
        name === "housingAllowance" ||
        name === "commutingAllowance" ||
        name === "paidLeave"
          ? parseInt(value) || 0 // 数値フィールドの場合、数値に変換(不正な入力は0とする)
          : value,
    }));

...prevState??
name??
「…」ってこっちがそうなるわ!!ってことでアレルギーが出たので
避けて通ってましたが、純粋にInputの個数分useStateを使っていたら
シンプルに非効率なので下記で説明しようず。

ReactのフォームをuseStateで管理

まずいくつかの前提条件があるので復習しよう。

スプレッド構文

スプレッド構文は「...spread」みたいに・・・を三つ並べる表記のことです。
これが何を意味するかというと、ちょっと下記のコードで結果を見てみると

const foo = [1, 2];

// 配列のクローン
const bar = [...foo]; // => [1, 2]

// 要素を追加して新しい配列を生成
const baz = [...foo, 3, 4]; // => [1, 2, 3, 4]

// 配列をマージ
const hoge = [...foo, ...bar]; // => [1, 2, 1, 2]

・オブジェクトの場合

const foo = { a: 1, b: 2 };
console.log(foo); // => { a: 1, b: 2 }

// オブジェクトのクローン
const bar = { ...foo }; // => { a: 1, b: 2 }

// プロパティを追加した新しいオブジェクトの生成
const baz = { ...foo, c: 3 }; // => { a: 1, b: 2, c: 3 }

// オブジェクトのマージ
const hoge = { ...foo, ...{ c: 3, d: 4 } }; // => { a: 1, b: 2, c: 3, d: 4 }

// 元のオブジェクトに同名プロパティがある場合は置き換わる
const fuga = { ...foo, b: 3 }; // => { a: 1, b: 3 }
const piyo = { ...foo, ...{ a: 3, b: 4 } }; // => { a: 3, b: 4 }

これはなにをやっちょるかというと(方言ごめんなさい)
「…spread」は既存のデータを保持しているて新たなオブジェクトまたは配列を作っている...
はい、わけわかめですね。。。
ってか成男必要なかろーがね!!
とか
わざわざまどろっこしい表記つかうんじゃねー!!
っていうのか昔の私でしたが、フォーム入力などの管理をする場面にこいつは力をはっきするのです。

実際の<Input>の中身は?

                      <div className="grid grid-cols-4 items-center gap-4">
                        <Label htmlFor="name" className="text-right">
                          氏名
                        </Label>
                        <Input
                          name="name"
                          defaultValue={editingEmployee.name}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                      </div>
                      <div className="grid grid-cols-4 items-center gap-4">
                        <Label htmlFor="birthDate" className="text-right">
                          生年月日
                        </Label>
                        <Input
                          type="date"
                          name="birthDate"
                          defaultValue={editingEmployee.birthDate}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                        <Label htmlFor="basicSalary" className="text-right">
                          基本給
                        </Label>
                        <Input
                          name="basicSalary"
                          defaultValue={editingEmployee.basicSalary}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                        <Label htmlFor="jobAllowance" className="text-right">
                          職務手当
                        </Label>
                        <Input
                          name="jobAllowance"
                          defaultValue={editingEmployee.jobAllowance}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                        <Label
                          htmlFor="housingAllowance"
                          className="text-right"
                        >
                          住宅手当
                        </Label>
                        <Input
                          name="housingAllowance"
                          defaultValue={editingEmployee.housingAllowance}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                        <Label
                          htmlFor="commutingAllowance"
                          className="text-right"
                        >
                          通勤手当
                        </Label>
                        <Input
                          name="commutingAllowance"
                          defaultValue={editingEmployee.commutingAllowance}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                        <Label htmlFor="paidLeave" className="text-right">
                          有給日数
                        </Label>
                        <Input
                          name="paidLeaveRemaining"
                          defaultValue={editingEmployee.paidLeaveRemaining}
                          onChange={handleInputChange}
                          className="col-span-3"
                        />
                      </div>
                    </div>

Inputが入力されたときににはonChangeメソッドをつかってhandleInputChange関数を呼び出しているのですが、ここで先ほどのuseStateをもう一度みてみよう

const [editingEmployee, setEditingEmployee] = useState({
    _id: "",
    name: "",
    birthDate: "",
    basicSalary: 0,
    jobAllowance: 0,
    housingAllowance: 0,
    commutingAllowance: 0,
    paidLeaveRemaining: 0,
  });
 const { name, value } = e.target;
    setEmployeeInfo((prevState) => ({
      ...prevState,
      [name]:
        name === "basicSalary" ||
        name === "jobAllowance" ||
        name === "housingAllowance" ||
        name === "commutingAllowance" ||
        name === "paidLeave"
          ? parseInt(value) || 0 // 数値フィールドの場合、数値に変換(不正な入力は0とする)
          : value,
    }));

イベントの属性にはほかにもname属性というものがありこれがかなりキーとなりまして、

setEmployeeInfo((prevState) => ({
      ...prevState,
      [name]:
        name === "basicSalary" ||
        name === "jobAllowance" ||
        name === "housingAllowance" ||
        name === "commutingAllowance" ||
        name === "paidLeave"
    ・
    ・
    ・

...prevStateはさっき習った通りで今までの情報を保持している
もしマウントした直後だとこのprevStateはuseStateの初期値である

    _id: "",
    name: "",
    birthDate: "",
    basicSalary: 0,
    jobAllowance: 0,
    housingAllowance: 0,
    commutingAllowance: 0,
    paidLeaveRemaining: 0,

になる。
そこから例えばname="name"とあるInputフォームに
田中太郎を入力すると

    _id: "",
    name: "田中太郎",
    birthDate: "",
    basicSalary: 0,
    jobAllowance: 0,
    housingAllowance: 0,
    commutingAllowance: 0,
    paidLeaveRemaining: 0,

となりname以外は...prevStateで既存のままで、nameだけ変更されるようになる。
それをevent.nameでどこのInputが変更されたかを判別するためにe.target.nameを使用して、
useStateのプロパティ名と等しいものに値を更新する!っという流れである。

終わりに

これでuseStateが一個で管理できることにより、
コードがすっきりして可読性が上がると思います。
さらに次のステップとしては、現在はuseStateを使っているのでInputの内容を変えるたびにレンダリングが走っている。例えばだがformページにおいては特に入力たびにAPIをたたくことはないがもしかして入力日にするたびに外部の課金制APIをたたいていたら大量のAPI接続をしてしまい、こりゃいくらあっても足りなくなります。
次回はそれを回避するためにuseRefのお話ができたらなぁと思います。

それでは皆様
「今日もいい夢見てね」

Discussion