【WEB】Reactの各種Stateにおける制約違反とエラーの原因まとめ

# はじめに
React の `useState` や `useReducer` などのState管理には、いくつかの制約が存在します。  
これらの制約を破るとエラーが発生し、意図しない動作の原因となります。  
私自身も実装中に制約違反でエラーを発生させてしまった為、代表的なStateの制約違反とそのエラーについて、纏めておきたいと思います。

## 1. `useState` の制約違反
### 1-1. State を直接変更してしまう
#### エラー内容
```tsx
const [count, setCount] = useState(0);

// 直接変更しようとするとエラーの原因に
count = count + 1; 
```
#### エラー原因
- `useState` で管理されている変数を直接変更すると、React が状態変更を検知できず、意図しない動作につながる。

#### 正しい実装方法
```tsx
setCount(count + 1);
```
または、以下のように関数で実装する
```tsx
setCount(prevCount => prevCount + 1);
```

---

### 1-2. `useState` の更新を同期的に期待する
#### エラー内容
```tsx
const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(count + 1);
  console.log(count); // 期待通りの値にならない
};
```
#### エラー原因
- `useState` の更新は非同期で行われるため、`setState` を実行しても即座には反映されない。

#### 正しい実装方法
```tsx
const handleClick = () => {
  setCount(prevCount => prevCount + 1);
  console.log(count); // 前の値が表示されるが、Reactの更新後には期待通りに反映される
};
```

## 2. `useEffect` の制約違反
### 2-1. `useEffect` の中で `setState` を適切に管理しない
#### エラー内容
```tsx
const [data, setData] = useState(null);

useEffect(() => {
  fetch("/api/data")
    .then(res => res.json())
    .then(json => setData(json)); // `setData` が無限ループを引き起こす可能性あり
}, [data]); // 依存配列が間違っている
```
#### エラー原因
- `data` を `useEffect` の依存配列に含めると、`setData(json)` のたびに `useEffect` が再実行されてしまい、無限ループになる。

#### 正しい実装方法
```tsx
useEffect(() => {
  fetch("/api/data")
    .then(res => res.json())
    .then(json => setData(json));
}, []); // 初回のみ実行されるように修正
```

## 3. `useReducer` の制約違反
### 3-1. Reducer 内で State を直接変更してしまう
#### エラー内容
```tsx
const reducer = (state, action) => {
  state.count += 1; //　直接変更
  return state;
};
```
#### エラー原因
- `useReducer` の `state` は **不変（immutable）** であるべき。  
  - 直接変更すると、React が変更を検知できず、更新が反映されない。

#### 正しい実装方法
```tsx
const reducer = (state, action) => {
  return { ...state, count: state.count + 1 }; // 新しいオブジェクトを作成
};
```

## 4. `useRef` の制約違反
### 4-1. `useRef` を State のように使おうとする
#### エラー内容
```tsx
const countRef = useRef(0);

const handleClick = () => {
  countRef.current += 1;
  console.log(countRef.current); // 値は更新されるが、コンポーネントの再レンダリングが発生しない
};
```
#### 問題点
- `useRef` の値は変更できるが、**React のレンダリングをトリガーしない**。
- `useState` と違い、値が変更されても UI には反映されない。

#### 正しい実装方法
```tsx
const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(prevCount => prevCount + 1); // 正しく UI を更新
};
```

## 5. `useEffect` 内で `setState` を誤ったタイミングで実行
### 5-1. `useEffect` 内で `setState` を条件なしで呼び出す
#### エラー内容
```tsx
const [count, setCount] = useState(0);

useEffect(() => {
  setCount(count + 1); // 毎回 `useEffect` が発火 → 無限ループ
}, [count]);
```
#### エラー原因
- `setCount(count + 1)` を `useEffect` 内で呼ぶと、`count` の変更によって `useEffect` が再実行され、無限ループが発生する。

#### 正しい実装方法
```tsx
useEffect(() => {
  if (count < 5) { // 条件を追加
    setCount(count + 1);
  }
}, [count]);
```

## まとめ
| **制約違反** | **原因** | **解決策** |
|------------|--------|------------|
| **State を直接変更** | React が変更を検知できない | `setState` を使って更新 |
| **State の更新を同期的に期待** | `useState` は非同期で動作する | `setState(prev => prev + 1)` を使う |
| **`useEffect` の依存配列ミス** | 無限ループの原因になる | 依存配列を適切に設定 |
| **Reducer で State を直接変更** | React の不変性ルール違反 | `return { ...state, newValue }` |
| **`useRef` を State のように使用** | `useRef` はレンダリングをトリガーしない | `useState` を使う |
| **`useEffect` 内で `setState` を条件なしで実行** | 無限ループになる | 条件付きで `setState` を呼ぶ |

## さいごに
ダイエットが順調に進行中です、減量目標まで50％達成・・・！
健康管理も実装も全力で対応していきますー！