Reactの配列の一部のStateを変更したときに再レンダリングするImmer.jsを使ってみる

# はじめに

Custom Elementsをやっている中で配列の一部を変更してレンダリングしようとしておりLitのドキュメントを見ていたところ`Immer`というライブラリが紹介されていたので確かめてみました

このライブラリを利用することで配列自体の変更でなく、その中の1要素の変更でも再レンダリングが行えるようになります

# やり方

今回は、ボタンをクリックしたらメンバーの2人目のメールアドレスが変更されるようなコードを書いていきます

```tsx:index.tsx
import { useState } from "react";
import "./styles.css";

class Member {
  constructor(
    readonly id: Number,
    readonly name: String,
    readonly email: String
  ) {}
}

export default function App() {
  const [members, setMembers] = useState<Member[]>([
    new Member(1, "yamada", "yamada@example.com"),
    new Member(2, "tanaka", "tanaka@example.com")
  ]);

  const changeEmail = () => {
    console.log("click button");
    members[1] = new Member(2, "tanaka", "chnage@example.com");
    setMembers(members);
  };

  return (
    <div className="App">
      {members.map((member, i) => {
        return (
          <div key={i}>
            <h1>{member.name}</h1>
            <h2>{member.email}</h2>
          </div>
        );
      })}
      <button onClick={() => changeEmail()}>Change Tanaka Email</button>
    </div>
  );
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810513/3283fad8-b22d-2793-a08e-193fd82921a7.png)

この状態だともちろん変更は検知されません
`setMembers`に新たな配列を代入しないと参照元が同じのため変更として判断されません

そこで、`Immer`を利用していきます。

https://immerjs.github.io/immer/example-setstate

```tsx:index.tsx
import { useCallback, useState } from "react";
import "./styles.css";
import { produce } from "immer";

class Member {
  constructor(
    readonly id: Number,
    readonly name: String,
    readonly email: String
  ) {}
}

export default function App() {
  const [members, setMembers] = useState<Member[]>([
    new Member(1, "yamada", "yamada@example.com"),
    new Member(2, "tanaka", "tanaka@example.com")
  ]);

  const changeEmail = useCallback(() => {
    console.log("click button");
    setMembers(
      produce((draft) => {
        draft[1] = new Member(2, "tanaka", "change@example.com");
      })
    );
  }, []);

  return (
    <div className="App">
      {members.map((member, i) => {
        return (
          <div key={i}>
            <h1>{member.name}</h1>
            <h2>{member.email}</h2>
          </div>
        );
      })}
      <button onClick={() => changeEmail()}>Change Tanaka Email</button>
    </div>
  );
}
```

ポイントは`useCallback`の中で、`produce`関数を使うことです
これで配列の中の1要素の変更を検知して再レンダリングが行われます

# 注意

ちなみに以下は動かないです
配列の中の1要素の変更でなく、配列の中のメンバーの中の要素を更新しようとしています
この場合は、その中でも`produce`が必要になります

```tsx:index.tsx
import { useCallback, useState } from "react";
import "./styles.css";
import { produce } from "immer";

class Member {
  constructor(
    readonly id: Number,
    readonly name: String,
    readonly email: String
  ) {}
}

export default function App() {
  const [members, setMembers] = useState<Member[]>([
    new Member(1, "yamada", "yamada@example.com"),
    new Member(2, "tanaka", "tanaka@example.com")
  ]);

  const changeEmail = useCallback(() => {
    console.log("click button");
    setMembers(
      produce((draft) => {
        // Memberの1要素の変更は検知できない
        draft[1].email = "change@exaple.com";
      })
    );
  }, []);

  return (
    <div className="App">
      {members.map((member, i) => {
        return (
          <div key={i}>
            <h1>{member.name}</h1>
            <h2>{member.email}</h2>
          </div>
        );
      })}
      <button onClick={() => changeEmail()}>Change Tanaka Email</button>
    </div>
  );
}
```

# おわりに

便利になっているのかな？という感じはしますが、少しばかりコードは読みやすくなっている気がします。
しかし、ライブラリを知らない人が多いと思うので自分だけが使うなら良いのかなといったいんしょうをもちましｔ

# 参考

https://immerjs.github.io/immer/example-setstate