🦉

Next.js+SWRでGraphQL!

4 min read

作ったもの

https://github.com/TakahiroHimi/Next-SWR-sample

実行方法は README.md をご確認ください。

実行するとトップページに二つリンクが表示されます。


countriesをクリックすると国名の一覧を取得して表示します。

取得先はPublic GraphQL APIsに掲載されている一般公開されているエンドポイントです。
今回動作確認用に使わせていただきます。


issuesをクリックするとoctocat さんの Hello-World リポジトリの Issueを 100 件取得して表示します。
取得先はGitHub の GraphQL APIです。

簡単に解説

以下のソースはサンプルソースから部分的に抜粋したものです

contries

pages/countries.tsxで全て完結しています。
重要なとこだけかいつまんで解説すると

pages/countries.tsx
import { request } from "graphql-request";
import useSWR from "swr";

const API = "https://countries.trevorblades.com"; // GraphQLエンドポイントのURL

const getCountries = () => {
  const { data, error } = useSWR<FetchData>(query, (query) => //👈ポイント
    request(API, query)
  );

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return data.countries.map((country) => (
    <li key={country.code}>{country.name}</li>
  ));
}

useSWRの第一引数に GraphQL の query 文字列、第二引数に Fetcher を設定しています。
戻り値のdataには取得データが格納され、errorにはエラー情報が格納されます。
またそれぞれの状態から Loading 状態も判定可能です。

issues

こちらはリクエストする際に header や variable が必要な場合のサンプルです。
事前にGitHub のアクセストークンを取得して.env.localを設定しておく必要があります(詳しくはREADME.md参照)。
こちらもかいつまんで解説すると

/pages/issues.tsx
import { GraphQLClient } from "graphql-request";
import useSWR from "swr";

const API = "https://api.github.com/graphql"; // GraphQLエンドポイントのURL
const repositoryOwner = "octocat";            // 取得するリポジトリ所有者のユーザー名
const repositoryName = "Hello-World";         // 取得するリポジトリの名前
const issuesFirst = 100;                      // 取得するIssueの数

const getIssues = () => {
  const client = new GraphQLClient(API, {     //👈ポイント①
    headers: {
      Authorization:
        "bearer " + process.env.NEXT_PUBLIC_GITHUB_PERSONAL_ACCESSTOKEN,
    },
  });

  const { data, error } = useSWR<FetchData>(
    [getRepositoryQuery, repositoryOwner, repositoryName, issuesFirst],
    (query, owner, name, first) =>            //👈ポイント②
      client.request(query, {
        repositoryOwner: owner,
        repositoryName: name,
        issuesFirst: first,
      })
  );

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return data.repository.issues.edges.map((issue) => (
    <li key={issue.node.id}>{issue.node.title}</li>
  ));
}

(ポイント ①)header と variable の設定が必要なので Fetcher にはGraphQLClientを使用しています。
(ポイント ②)useSWRの第一引数は query と各 variable を配列で渡し、第二引数でそれらを使用します。
ここが大事なポイントで SWR は取得した値をキャッシュしますが、useSWR の第一引数に与えた値をキャッシュの Key として扱います
もし variable の値を第一引数で渡していない場合、同じ query を variable 違いで実行したとき、本来サーバーへリクエストすべきところがキャッシュの値を返却してしまいます。
仕様上絶対に毎回同じ値になる variable は引数で渡す必要は無いですが(今回のケースだと「repositoryOwnerrepositoryNameは毎回変わるがissuesFirstは毎回 100 で固定」 など)、
バグに直結する部分でもあるので variable は必ず引数で渡す、などルールを決めても良いかもしれません。

この Key はデータ更新(mutation)の際にも使用することになります。
その内容については以下の記事で実践していいます。

https://zenn.dev/thim/articles/9ff415100b892d23aa59

最後に

実務(CRA)では GraphQL クライアントに ApolloClient を使用しておりかなり便利で気に入っているのですが、Next.js で ApolloClient を使用する場合ページのレンダリングと API リクエストのタイミングを注意深く意識してコーディングしなければなりません
そのあたりの内容は以下のブログで解説されています。

https://www.apollographql.com/blog/getting-started-with-apollo-client-in-next-js/
Next.js においては SWR の方が気楽に書けるかなと思いますが、GraphQL を扱う上で Normalized cache の有無の差は非常に大きいため、どちらを採用するかは実装内容と照らし合わせてよく検討する必要があります。

Discussion

ログインするとコメントできます