配列をプロパティにもつオブジェクト型のstate更新(React x Typescript)

# はじめに
みなさん、こんにちは **torihazi**です！
今日はいつもとは異なり少し複雑なState更新の方法について
自分なりにまとめてみました！

環境は React x typescriptです！

# どう言うこと？
Reactを触り始めてから "useState"を使った State管理と言うものに
よくお世話になっています。
今までは 
```
単純なboolean型とか
const [isOpen, setIsOpen] = useState<boolean>(false);

1階層のオブジェクト型とか
const [user, setUser] = useState<User>({
    name: "hoge",
    email: "hoge@mail.com",
    age: 24,
    isActive: true,
}
```
くらいしかまともに触っていなかったのですが、最近タイトルに書いたようなstateを扱って
そういえばこれどうやってstate更新するんだ？と悩んだので
まとめてみました！

```
  const [user, setUser] = useState<User>({
    name: "John Doe",
    addresses: [
      { city: "Tokyo", country: "Japan", isEditable: false },
      { city: "New York", country: "USA", isEditable: false },
    ],
  });
```

# 完成版
![2024-08-1223.04.00-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2567812/c225d2bc-19d7-04ae-c0c1-cf8293333c69.gif)
tailwindcssで多少なりとも手心加えたので許してください。

# コード
```tsx:App.tsx
import React, { useState } from "react";

interface Address {
  city: string;
  country: string;
  isEditable: boolean;
}

interface User {
  name: string;
  addresses: Address[];
}

function App() {
  const [user, setUser] = useState<User>({
    name: "John Doe",
    addresses: [
      { city: "Tokyo", country: "Japan", isEditable: false },
      { city: "New York", country: "USA", isEditable: false },
    ],
  });

  // const updateName = (newName: string) => {
  //   setUser((prevUser) => ({
  //     ...prevUser,
  //     name: newName,
  //   }));
  // };

  const addAddress = () => {
    setUser((prevUser) => ({
      ...prevUser,
      addresses: [
        ...prevUser.addresses,
        { city: "sample", country: "sample", isEditable: false },
      ],
    }));
  };

  const updateAddress = (index: number, newAddress: Address) => {
    setUser((prevUser) => ({
      ...prevUser,
      addresses: prevUser.addresses.map((address, i) =>
        i === index
          ? { ...address, city: newAddress.city, country: newAddress.country }
          : address
      ),
    }));
  };

  const toggleAddressEditable = (index: number) => {
    setUser((prevUser) => ({
      ...prevUser,
      addresses: prevUser.addresses.map((address, i) =>
        i === index ? { ...address, isEditable: !address.isEditable } : address
      ),
    }));
  };

  const delAddress = (index: number) => {
    setUser((prevUser) => ({
      ...prevUser,
      addresses: prevUser.addresses.filter((_, i) => i !== index),
    }));
  };

  return (
    <>
      <h3 className="text-xl">ユーザ情報</h3>
      <div className="flex flex-col">
        <div className="flex items-center">
          <p className="mr-2">{user.name}</p>
          <button
            className="flex-grow-0 border p-0.5 rounded hover:bg-slate-300 mr-2 transition"
            onClick={() => addAddress()}
          >
            住所追加
          </button>
        </div>
        {user.addresses.map((address, index) => (
          <div key={index} className="flex items-center">
            {address.isEditable ? (
              <>
                <input
                  value={address.city}
                  onChange={(e) =>
                    updateAddress(index, { ...address, city: e.target.value })
                  }
                  placeholder="City"
                  className="border"
                />
                <input
                  value={address.country}
                  onChange={(e) =>
                    updateAddress(index, {
                      ...address,
                      country: e.target.value,
                    })
                  }
                  placeholder="Country"
                  className="border"
                />
                <button
                  onClick={() => toggleAddressEditable(index)}
                  className="border p-0.5 rounded hover:bg-slate-300 mr-2 transition"
                >
                  完了
                </button>
              </>
            ) : (
              <>
                <div className="mr-2">{`第${index + 1}住所 首都: ${
                  address.city
                } 国名: ${address.country}`}</div>
                <button
                  className="border p-0.5 rounded hover:bg-slate-300 mr-2 transition"
                  onClick={() => toggleAddressEditable(index)}
                >
                  編集
                </button>
                <button
                  className="border p-0.5 rounded hover:bg-slate-300 transition"
                  onClick={() => delAddress(index)}
                >
                  削除
                </button>
              </>
            )}
          </div>
        ))}
      </div>
    </>
  );
}

export default App;
```

# やりたかったこと
```
  const updateAddress = (index: number, newAddress: Address) => {
    setUser((prevUser) => ({
      ...prevUser,
      addresses: prevUser.addresses.map((address, i) =>
        i === index
          ? { ...address, city: newAddress.city, country: newAddress.country }
          : address
      ),
    }));
  };
```
このオブジェクト型のstateで配列をプロパティに持つ時、こういう方法があるみたいです。
なるへそ。と言う感じです。
{}にしたり、スプレッド構文を使うために[]で囲うのか、{} なのか、こんがらがってきますね。
今は多少なれた、はずです。

# 終わりに
Todoリストにもなりうるのではと書いていて思いました。
やっぱりAddressの型の中に関係ない"isEditable"含ませない方がいいんですかね。
他の方法あれば教えてください。
あとtypescriptだと、vscodeでいたるところに赤い波線出てきてビビりますね。
型追加したじゃん！とかで毎回泣いてます。
anyとかにしたら消えるんですけど、それだと意味ないらしいので
頑張って食らいついてます。
早く慣れて欲しいものです。。

https://github.com/torihazi/pr-useState