サーバー待ちのローディング表示をApollo Client + Reactで行う
導入
Apollo Client で構築したアプリケーションで、 GraphQL サーバーからの応答待ちの間に、いわゆる「スピナー」などのローディング表示を行いたい場合があります。
React Suspense は使わないの?
Apollo Clientはバージョン3.8でSuspenseに対応しました https://www.apollographql.com/docs/react/data/suspense
ローディング表示を行いたい場合、React 18 以降ではSuspenseが選択肢に上がるでしょう。ところが、GitHub: apollo-client - Adding React suspense + data fetching supportにあるように、Apollo Client 公式ではまだ Suspense との統合方法を紹介していません。
Apollo Client 公式が推奨する方法が決まるまでは、本記事のように Suspense を使わない従来のローディング表示が参考になると思います。
事前準備
チュートリアルで作成するアプリケーション全体の構成はこちらです。
こちらの構成を作っていきましょう。
事前準備 1. 作業ディレクトリ
まずは本チュートリアル用のディレクトリを作成します。
mkdir tutorial-apollo-client-loadin。
cd tutorial-apollo-client-loading
全体のディレクトリ構成は最終的に以下のようになります。
tutorial-apollo-client-loading
+- server # Apollo GraphQL Server
+- client # React Client
それでは次の手順で、上記のディレクトリ構成のうち、server
ディレクトリから作成していきましょう。
事前準備 2. GraphQL サーバー
2 つのプロセス、Apollo Server と Server 側の GraphQL codegen を立ち上げます。
# 一度に全部コピペで実行できます
mkdir server
cd server
# node.js setup
npm init -y
echo "node_modules" > .gitignore
TypeScript を導入して、ts-node-dev でサーバープロセスを立ち上げる準備をしましょう。
# install and initialize typescript
npm install --save-dev typescript
npx tsc --init
# ts-node-dev: watch and restart a TypeScript server
npm install --save-dev ts-node-dev
npm pkg set scripts.start="ts-node-dev --watch src/*,data/*,./schema.gql --respawn src/index.ts"
Apollo Server と GraphQL Codegen を導入します。
# apollo server
npm install apollo-server graphql
# install and setup graphql-codegen
npm install --save-dev @graphql-codegen/cli
# ここで npx graphql-code-generator init を行わず、後でcodegen.tsを生成。理由は後述。
npm install --save-dev \
@graphql-codegen/typescript \
@graphql-codegen/typescript-resolvers
npm pkg set scripts.generate="graphql-codegen --config codegen.yml --watch ./schema.gql,./data/*" # update generate script
@graphql-codegen/cli
をインストールした後、通常はコマンドnpx graphql-code-generator init
によってファイルcodegen.ts
を生成しますが、そうすると対話モードに入ってしまい手入力が増えるのと、結局は生成された config.ts を上書き変更することになるので、以下のコマンドで config.ts を作成します。
cat << EOF > codegen.ts;
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: "schema.gql",
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript", "typescript-resolvers"],
config: {
avoidOptionals: true,
},
},
},
hooks: {
afterOneFileWrite: ["npx prettier --write"],
},
};
export default config;
EOF;
Apollo Server 立ち上げに必要な初期ソースコードを追加しましょう。
# 開発ワークスペースのルートディレクトリに移動
# こうしないと、次のgit applyがエラーの理由も知らせず失敗する可能性あり
cd ../
curl https://github.com/richardimaoka/tutorial-apollo-client-loading/commit/65243e4ea244df1fd0bac1aeda9030643278e16c.patch \
| git apply -v -
上記コマンドによる変更内容は以下のリンクのとおりです。
それでは、サーバーサイドの 2 つのプロセスを立ち上げましょう。
cd server
npm run codegen # Serer GraphQL codegen
cd server
npm run start # Apollo Server
2 つめのコマンドで以下のように GraphQL サーバーが立ち上がります。
🚀 Server ready at: http://localhost:4000/
ブラウザから http://localhost:4000/ を開いて、Query your server ボタンを押して下さい。
クエリを実行して、結果が返ってくれば成功です。
実行する GraphQL クエリはこちらです。
{
employees {
jobTitle
name
picturePath
}
}
事前準備 3. React クライアント
ここからは、サーバー側の 2 つのプロセスに追加して、クライアント側プロセスを 2 つ立ち上げます。
npx create-react-app client --template typescript
cd client
npx prettier --write .
# apollo client
npm install @apollo/client graphql
# install and setup graphql-codegen
npm install --save-dev @graphql-codegen/cli
# ここで npx graphql-code-generator init を行わず、後でcodegen.tsを生成。理由は後述。
npm install --save-dev \
@graphql-codegen/typescript-operations \
@graphql-codegen/typescript \
@graphql-codegen/typescript-react-apollo
npm pkg set scripts.codegen="graphql-codegen --config codegen.ts --watch src/\\*_/_.tsx,../server/schema.gql"
@graphql-codegen/cli
をインストールした後、通常はコマンドnpx graphql-code-generator init
によってファイルcodegen.ts
を生成しますが、そうすると対話モードに入ってしまい手入力が増えるのと、結局は生成された config.ts を上書き変更することになるので、以下のコマンドで config.ts を作成します。
cat << EOF > codegen.ts
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: "../server/schema.gql",
documents: "src/**/*.tsx",
generates: {
"src/generated/graphql.ts": {
plugins: [
"typescript",
"typescript-operations",
"typescript-react-apollo",
],
config: {
avoidOptionals: true,
},
},
},
hooks: {
afterOneFileWrite: ["npx prettier --write"],
},
};
export default config;
EOF
続いて、後ほどスピナーを表示するのに利用する FontAwesome も導入しましょう。公式ドキュメントの React 用セットアップ手順に従って以下を実行します。
# Add SVG Core
npm i --save @fortawesome/fontawesome-svg-core
# Add Icon Packages
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
# Add the React Component
npm i --save @fortawesome/react-fontawesome@latest
React + Apollo Client 立ち上げに必要な初期ソースコードを追加しましょう。
# 開発ワークスペースのルートディレクトリに移動
# こうしないと、次のgit applyがエラーの理由も知らせず失敗する可能性あり
cd ../
curl https://github.com/richardimaoka/tutorial-apollo-client-loading/commit/aba557674d984551b8533d5b5b6cdd10109d9ae7.patch \
| git apply -v -
上記コマンドによる変更内容は以下のリンクのとおりです。
それでは、クライアントサイドの 2 つのプロセスを立ち上げましょう。
cd client
npm run codegen # Client GraphQL codegen
cd client
npm run start # React Apollo Client
ここで、ブラウザから https://localhost:3000 にアクセスして、以下のように表示されれば成功です。
サーバー待ちがある場合のローディング表示
いよいよサーバー待ちのためを表現するための遅延を導入し、クライアント側でローディング表示を行います。
サーバーサイドに遅延を挿入
# 開発ワークスペースのルートディレクトリに移動してから実行
curl https://github.com/richardimaoka/tutorial-apollo-client-loading/commit/4f982c7d0c6f4367835a52d5137e3694903f1f99.patch \
| git apply -v -
上記のコマンドによるソースコードの変更はこちらの通りです。
+ import { setTimeout } from "timers/promises";
...
search: async (_parent, _args, context, _info) => {
+ console.log("waiting started");
+ await setTimeout(3000, null);
+ console.log("waiting ended");
return context.Query.search;
},
await setTimeout(3000,null) はなに?
https://nodejs.org/api/timers.html#timers_timers_promises_api を利用した sleep 処理です。この Node.js の Timers Promises API は Node.js の 16 から安定版になりました。
以前は Stack Overflow - How can I wait In Node.js (JavaScript)? l need to pause for a period of timeで記載されているように、以下のようなイディオムを書いて Sleep を表現することが通例でした。
await new Promise((resolve) => setTimeout(resolve, 5000));
これは「何もせず 5000 ミリ秒後に resolve して終わる Promise」であり、それを await で待っている処理です。慣れてしまえば、あるいは Promise や async/await に詳しければ意図を読み取れるものの、できれば以下のようにかけたほうがスッキリします。
await setTimeout(5000, null);
Timers Promises API ならこれが実現できます。
それではサーバー側の遅延を確認してみましょう。ボタンを押して 3 秒ほど待ってからクエリ結果が返ってきています。
Apollo Server のログにはこのように表示されます。
🚀 Server ready at: http://localhost:4000/
waiting started
waiting ended
クライアントサイドにローディング表示を導入
# 開発ワークスペースのルートディレクトリに移動してから実行
curl https://github.com/richardimaoka/tutorial-apollo-client-loading/commit/c599fc6b3b29e54196a659ebcfa0e6e38e004871.patch \
| git apply -v -
上記のコマンドによるソースコードの変更はこちらの通りです。FontAwesome を利用したスピナーを追加しています。
+ import { faSpinner } from "@fortawesome/free-solid-svg-icons";
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
...
export const EmployeeListing = () => {
const { loading, error, data } = useGetEmployeesQuery();
- if (loading) return <>Loading...</>;
+ if (loading)
+ return (
+ <div>
+ <FontAwesomeIcon icon={faSpinner} size={"4x"} spin={true} />
+ </div>
+ );
if (error) return <>error happened</>;
if (!data || !data.employees) return <>empty data</>;
...
完成形はこちらのとおりです。無事 GraphQL サーバーからの応答を待つ間スピナーによるローディング表示を行う React コンポーネントが作動しました。
Discussion