Laravel + Inertia + React で複数setDataを連続して使うときの注意点

Laravel + Inertia で開発する場合、フォームの送信はInertiaのuseFormヘルパーを使うことが多いかと思います。

GitHub

Reactで複数のsetDataを連続して使うときにうまく動作しない現象がありました。

例えば、ボタンを押すと複数のinputの数字がカウントアップするという画面をイメージしてください。マニュアル通りに記述すると下記のようになります。

import { useForm } from '@inertiajs/react';

export default function Page() {
  const { data, setData } = useForm({
    a: 1,
    b: 1,
    c: 1,
  });

  const countUp = () => {
    setData('a', data.a + 1);
    setData('b', data.b + 1);
    setData('c', data.c + 1);
  }

  return (
    <>
      <button onClick={countUp}>
        Count up
      </button>
      <input value={data.a} />
      <input value={data.b} />
      <input value={data.c} />
    </>
  );
}

しかし、この書き方だと最後のcだけカウントアップされて、aとbは変化しません。

このように、複数のsetDataを連続して使うと、最後のsetDataしか動作しません。

解決策

解決策は、setDataをこのように記述します。

  const countUp = () => {
    setData(data => ({...data, a: data.a + 1}));
    setData(data => ({...data, b: data.b + 1}));
    setData(data => ({...data, c: data.c + 1}));
  }

これで、すべてのsetDataが正しく動作し、a、b、c全てがきちんとカウントアップされます。どうやらこれはバグではなく、Reactのstateの挙動によるものらしいです。

prevStateを使わない場合は、新しい値をオブジェクトにして渡すこともできます。ただし、この書き方ではdataオブジェクトがそのまま上書きされるのでご注意ください。setDataに渡されない値(下記の場合はc)はdataオブジェクトから削除されます。

  const countUp = () => {
    setData({
        a: 2,
        b: 2,
    });
  }

Laravel Precognitionでの記述方法

なお、Laravel PrecognitionのuseFormヘルパーでも同じ現象が起きます。Precognitionの場合は下記のように記述します。

import { useForm } from 'laravel-precognition-react-inertia';

export default function Page() {
  const form = useForm('post', route('posts.store'), {
    a: 1,
    b: 1,
    c: 1,
  });
  type DataType = typeof form.data;

  const countUp = () => {
    form.setData((data: DataType) => ({...data, a: data.a + 1}));
    form.setData((data: DataType) => ({...data, b: data.b + 1}));
    form.setData((data: DataType) => ({...data, c: data.c + 1}));
  }
...

TypeScriptの場合、タイプエラーが表示されるので型を定義してあげます。

コンソールエラーなども出してくれないので、原因の追求にめちゃめちゃ苦労しました。

以上です。

コメント

タイトルとURLをコピーしました