React で、state 内の深くネストされたオブジェクトの更新には Immer というライブラリの使用が推奨されていますが、インストールや import で読み込む必要があり少々面倒で、組み込みでないため Next.js のアップデートの際など確実に常に安定して動作するかというのも疑問で、これを使わずにビルトイン関数のみでうまくやれないのかと。

結論、割と新しめの structuredClone() というメソッドを使うことで解決できるよという解説記事になります。

structuredClone() メソッドで state 内のオブジェクトを更新する方法

では早速、structuredClone() を使い、state 内のオブジェクトを更新する方法を解説します。

まずは useState をインポートします。

import { useState } from 'react';

useState() を使い、以下のように state を定義します。

const [obj, setObj] = useState({a: 100, b: 200});

以下のようにすると、ボタンをクリックする度に obj.a が 5 加算されるようになります。

function handleClick() {
  const nextObj = structuredClone(obj);
  nextObj.a += 5;
  setObj(nextObj);
}

return (
  <button onClick={handleClick}>5増やすボタン</button>
);

structuredClone() は、引数に指定した値のディープコピーを返します。つまり、返り値は元の値とは完全に切り離された別のオブジェクトとなるため、変更しても元の値は一切影響を受けません。そのため、コピーしたオブジェクトに変更を加えたのち、それを setState() に渡して値を更新することができます。

Immer を使って state 内のオブジェクトを更新する方法

比較として、上記で説明したことを Immer を使いやってみます。

まずはターミナルで以下のコードを実行し、Immer をインストールします。

npm install immer use-immer

useImmer をインポートします。

import { useImmer } from 'use-immer';

useImmer() を使い、以下のように state を定義します。

const [obj, updateObj] = useImmer({a: 100, b: 200});

以下のようにすると、ボタンをクリックする度に obj.a が 5 加算されるようになります。

function handleClick() {
  updateObj(draft => {
    draft.a += 5;
  });
}

return (
  <button onClick={handleClick}>5増やすボタン</button>
);

結論: Immer を使わずとも structuredClone() でよい

両者を比較すると、Immer を使うとロジックの部分は完結に書くことができますが(行数は変わらず、文字数が少なくなり端的にはなる)、インストールやインポートなどが少々手間となります。そして、実行速度はほぼ変わらないため、好きなほうを使うと良いという結論となります。structuredClone() の存在により、Immer を使う必要をそれほど感じないため、自分は structuredClone() 派かなと現状では思っています。参考になれば幸いです。