🛏️

【Apollo Client】cache.modifyを使ってキャッシュ単体のフィールドを更新する

2023/01/07に公開

過去2つの記事

1つ目の記事では
typename:idのキャッシュを削除するためにevictを使いました。
https://zenn.dev/jordan23/articles/2caf4ae0ac75ac

2つ目の記事は、
実行クエリ と その正規化された結果がROOT_QUERYに保存されている

上記をreadQueryで読み取り、writeQueryで変更を加えました。
useQueryでキャッシュがいつものように上書きされるイメージ。
https://zenn.dev/jordan23/articles/3d81b1ff9dea1f

そして今回の記事では
キャッシュ(1つのオブジェクト)のフィールドの値を更新したいと思います。

例えば以下のように、ROOT_QUERYでクエリの実行結果が保存されていた場合

__ref:"Player:23"

Player:{id}のキャッシュを更新することで、__ref:"Player:{id}"を結果としてもつクエリを使用している画面が更新されるか試します。

使うメソッドはこちら

cache.modify

InMemoryCacheのmodifyメソッドを使用することで、
キャッシュされた個々のフィールドの値を直接変更したり、フィールドを完全に削除したりすることができます。
https://www.apollographql.com/docs/react/caching/cache-interaction/#using-cachemodify

今回modifyを試すにあたり、前の2つの記事のコードに、編集ボタンを加えました。
この編集ボタンを押すことで、編集画面に遷移します。
遷移先ではidをもとに、player情報を取得しています。
リスト ※cache-firstで取得

編集画面

ここで行うことは名前の編集にともない、キャッシュの操作を行います。そしてリストに戻り、編集が反映されるようにします。

具体的には、
編集のミューテーションが行われた後に、
playerオブジェクト(キャッシュ)のnameフィールドのキャッシュ操作を行うことをやっていきます。

編集のミューテーション

export const UPDATE_PLAYER = gql`
  mutation updatePlayer($id: ID!, $name: String) {
    updatePlayer(id: $id, name: $name) {
      id
      name
    }
  }
`;

↑のポイントはidとnameフィールドを指定していることです。
ミューテーションもクエリと同様にキャッシュを保存するので、この場合Player:idのnameフィールドを、ミューテーションで取得した編集後のnameフィールドの値で更新することができます。

編集画面のコード

import { useMutation, useQuery } from "@apollo/client";
import React, { ChangeEvent, useState } from "react";
import { useParams } from "react-router-dom";
import { FETCH_PLAYER, UPDATE_PLAYER } from "../gql/player";

const Player = () => {
  const { id } = useParams();

  const [name, setName] = useState<string>("");

  useQuery(FETCH_PLAYER, {
    onCompleted(data) {
      setName(data.fetchPlayerById.name);
    },
    variables: {
      id,
    },
    fetchPolicy: "network-only",
  });

  const [updateName] = useMutation(UPDATE_PLAYER, {
    update(cache, data) {
      cache.modify({
        id: cache.identify(data.data.updatePlayer),
        fields: {
          name(cachedName) {
            return cachedName + "(modify!!!)";
          },
        },
      });
    },
    onCompleted(data) {
      alert("success!");
    },
  });

  const onClickUpdateName = async () => {
    await updateName({
      variables: {
        id,
        name,
      },
    });
  };

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
      />
      <button onClick={onClickUpdateName}>編集</button>
    </div>
  );
};

export default Player;

ポイントとなるコードは以下の部分です。

  const [updateName] = useMutation(UPDATE_PLAYER, {
    update(cache, data) {
      cache.modify({
        id: cache.identify(data.data.updatePlayer),
        fields: {
          name(cachedName) {
            return cachedName + "(modify!!!)";
          },
        },
      });
    },
    onCompleted(data) {
      alert("success!");
    },
  });
 id: cache.identify(data.data.updatePlayer),

まずidにidentifyメソッドを利用して、編集を行うキャッシュのキャッシュidを取得して指定します。

fields: {
          name(cachedName) {
            return cachedName + "(modify!!!)";
          },
        },

そして今回はnameフィールドを編集したいので、fieldsの中に関数を定義します。nameの引数にミューテーションを叩き終わった時点でキャッシュされたnameが入っているので、こちらを編集します。
今回は文字列(modify!!!)を連結させました。そしてその値を返して完了となります。

実行結果
編集前

編集後

そして実行結果としてPlayer:48を参照しているクエリを使っている画面は更新されています。

※ちょっと見づらいです。
編集画面
fetchPlayerById

一覧画面 cache-first
fetchPlayers

Discussion