1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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

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

やり方

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

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

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

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

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が必要になります

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>
  );
}

おわりに

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

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?