[React] 多次元配列(オブジェクト)のsetStateが2回実行されてしまう原因

スポンサーリンク

概要

ReactのuseStateフックを使って管理している多次元の配列やオブジェクトを更新する場合、元の配列・オブジェクトをディープコピーしなければならない。

シャローコピーだと破壊的変更となってしまい、StrictモードではsetStateが2回呼び出されてしまう。

検証用コード

import {useCallback, useState} from 'react'
export default function App() {

  const [arr, setArr] = useState([0]);
  const [arr2, setArr2] = useState([[0]]);
  const [obj, seObj] = useState({num:0});
  const [obj2, seObj2] = useState({sub: {num:0}});
  const handleClick = () => {
    setArr((prevArr)=>{
      const newArr= [...prevArr]
      newArr[0]++
      return newArr;
    })
    setArr2((prevArr2)=>{
      const newArr2= [...prevArr2] // シャローコピー
      newArr2[0][0]++
      return newArr2;
    })
    seObj((prevObj)=>{
      const newObj= {...prevObj}
      newObj.num++
      return newObj;
    })
    seObj2((prevObj2)=>{
      const newObj2= {...prevObj2} // シャローコピー
      newObj2.sub.num++
      return newObj2;
    })
  }
  return (
    <div className="App">
      <p>arr={arr[0]}</p>
      <p>arr2={arr2[0][0]}</p>
      <p>obj={obj.num}</p>
      <p>obj2={obj2.sub.num}</p>
      <button onClick={handleClick}>click</button>
    </div>
  );
}

検証用コード実行結果

arr=1
arr2=2
obj=1
obj2=2

検証用コード(ディープコピーに修正後)

import {useCallback, useState} from 'react'
export default function App() {

  const [arr, setArr] = useState([0]);
  const [arr2, setArr2] = useState([[0]]);
  const [obj, seObj] = useState({num:100});
  const [obj2, seObj2] = useState({sub: {num:100}});
  const handleClick = () => {
    setArr((prevArr)=>{
      const newArr= [...prevArr]
      newArr[0]++
      return newArr;
    })
    setArr2((prevArr2)=>{
      const newArr2 = JSON.parse(JSON.stringify(prevArr2)) // ディープコピー
      // または const newArr2 = prevArr2.map(subArr => [...subArr])
      newArr2[0][0]++
      return newArr2;
    })
    seObj((prevObj)=>{
      const newObj= {...prevObj}
      newObj.num++
      return newObj;
    })
    seObj2((prevObj2)=>{
      const newObj2 = JSON.parse(JSON.stringify(prevObj2)) // ディープコピー
      newObj2.sub.num++
      return newObj2;
    })
  }
  return (
    <div className="App">
      <p>{arr[0]}</p>
      <p>{arr2[0][0]}</p>
      <p>{obj.num}</p>
      <p>{obj2.sub.num}</p>
      <button onClick={handleClick}>click</button>
    </div>
  );
}

検証用コード(ディープコピーに修正後)実行結果

arr=1
arr2=1
obj=1
obj2=1

補足

JSON.parse(JSON.stringify(xxx))でのディープコピーは、Date型やFunction型などの値には使うことができない。

JavaScriptのDeepCopyでJSON.parse/stringifyを使ってはいけない - Qiita
JavaScriptでのDeepCopyはJqueryではextend, angular.js だと angular.copy を使って簡単にできてしまうようだけれど、標準実装としてはそもそも用意が…

参考サイト

Attention Required! | Cloudflare

関連書籍

コメント