【React】useStateでcannot assign to read only propertyエラーが発生する原因と解決方法

Reactを使っていると、useStateを使用する際にcannot assign to read only propertyというエラーに遭遇することがあります。このエラーは、特にオブジェクトや配列の状態を更新しようとしたときに頻発します。

この記事ではcannot assign to read only propertyエラーについて、以下の内容をサンプルコードを用いてわかりやすく解説します。

  • cannot assign to read only propertyエラーの原因
  • cannot assign to read only propertyエラーの解決方法

cannot assign to read only propertyエラーの原因

cannot assign to read only propertyエラーはuseStateで管理している状態オブジェクトや配列を直接変更しようとした場合に発生します。Reactでは、状態(state)はイミュータブル(不変性)を保つ必要があります。

例えば、以下のサンプルコードはcannot assign to read only propertyエラーが発生します。

import React, { useState } from 'react';

function App() {
  // 初期状態を Object.freeze() で凍結(これがないとエラーがでない)
  const [state, setState] = useState(Object.freeze({ count: 0 }));

  const handleClick = () => {
    state.count += 1; // オブジェクトを直接変更しようとする
    setState(state); // エラー発生
  };

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App;

上記のサンプルコードでは、useStateで管理している状態stateを直接変更してからsetStateを呼び出しているため、エラーが発生します。

cannot assign to read only propertyエラーの解決方法

cannot assign to read only propertyエラーを解決するには、useStateの状態更新時に元の状態を変更せず、新しいオブジェクトや配列を作成して状態を更新する必要があります。

以下にサンプルコードを示します。

import React, { useState } from 'react';

function App() {
  // 初期状態を Object.freeze() で凍結
  const [state, setState] = useState(Object.freeze({ count: 0 }));

  const handleClick = () => {
    // 元の状態をコピーして新しい変数を作成
    const newState = { ...state };
    newState.count += 1; // 必要なプロパティを更新

    setState(newState); // 新しい状態をセット
  };

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App;

上記のサンプルコードでは、スプレッド構文(…state)を使って、元の状態を新しいオブジェクトnewStateにコピーしています。その後、必要なプロパティだけを更新してをsetStateに渡しています。

以下に示すようにsetState関数の引数に「関数」を渡す方法(setState((prevState) => {...}))でもOKです。

import React, { useState } from 'react';

function App() {
  // 初期状態を Object.freeze() で凍結
  const [state, setState] = useState(Object.freeze({ count: 0 }));

  const handleClick = () => {
    setState((prevState) => ({
      ...prevState, // 元の状態を展開
      count: prevState.count + 1, // 必要なプロパティのみ更新
    }));
  };

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App;

配列の場合

次にuseStateで管理している状態が配列の場合において、cannot assign to read only propertyエラーが発生します。

import React, { useState } from 'react';

function App() {
  // 初期状態を Object.freeze() で凍結
  const [items, setItems] = useState(() => Object.freeze([1, 2, 3]));

  const handleAddItem = () => {
    items.push(4); // 配列を直接変更
    setItems(items); // 直接変更した配列をセット
  };

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
}

export default App;

上記のサンプルコードでは、items.push(4)で配列を直接変更しているため、cannot assign to read only propertyエラーが発生します。

解決するには配列を直接変更せず、新しい配列を作成して更新します。

import React, { useState } from 'react';

function App() {
  // 初期状態を Object.freeze() で凍結
  const [items, setItems] = useState(() => Object.freeze([1, 2, 3]));

  const handleAddItem = () => {
    const newItems = [...items, 4]; // 配列をコピーして新しい配列を作成
    setItems(newItems); // 新しい配列をセット
  };

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
}

export default App;

以下に示すようにsetState関数の引数に「関数」を渡す方法(setState((prevState) => {...}))でもOKです。

import React, { useState } from 'react';

function App() {
  // 初期状態を Object.freeze() で凍結
  const [items, setItems] = useState(() => Object.freeze([1, 2, 3]));

  const handleAddItem = () => {
    setItems((prevItems) => [...prevItems, 4]); // 元の配列を展開して新しい配列を作成
  };

  return (
    <div>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
}

export default App;

本記事のまとめ

この記事ではcannot assign to read only propertyエラーについて、以下の内容を説明しました。

  • エラーの原因
    • ReactのuseStateで管理しているオブジェクトや配列の状態を直接変更した場合に発生する
      • 例: オブジェクトのプロパティを直接変更(state.count += 1
      • 例: 配列を直接変更(items.push(4)
  • エラーの解決方法
    • イミュータブル(不変性)を保つ
    • 状態を更新する際は、元の状態を変更せず新しいオブジェクトや配列を作成する。

お読み頂きありがとうございました。