Apollo ClientはReactで使える状態管理ライブラリです。ローカルとリモートのデータをGraphQLで扱えます。本稿は、すでに公開した「React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる」の続編です。公式サイトの「Queries」で紹介された作例に、TypeScriptを採り入れ、アプリケーションは簡単にモジュール分けしてつくります。
useQuery
でデータを読み込む
お題とする公式作例は、CodeSandboxに公開されている「Queries > Example app final」です。モジュールはsrc/index.js
ひとつで、TypeScriptも使われていません。本稿では、Reactアプリケーションのひな形をCreate React Appでつくりましょう。やり方については、「React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる」の「Apollo Clientアプリケーションをつくる準備」をお読みください。
はじめの一歩は、以下のふたつのモジュール(src/App.tsx
とsrc/Dogs.tsx
)です(コード001)1。useQuery
フックを用いて、GraphQLの犬種データをドロップダウンメニュー(要素<select>
の子の<option>
)に加えました(詳しくは、前出「React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる」参照)。まだ、メニューを選んでも何も起こりません。ご参考までに、サンプル001をCodeSandboxに公開しました。
なお、クエリGET_DOGS
に収めたgql
に渡しているクエリ文字列先頭のquery GetDogs
はオペレーション名です。オペレーションタイプのキーワードquery
のあとにオペレーション名を添えます。省略しても構いません。けれど、加えることでコードはよりわかりやすくなるでしょう。
コード001■ドロップダウンメニューにGraphQLのデータを読み込む
import { ChangeEventHandler, useState } from 'react';
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
gql
} from '@apollo/client';
import { Dogs } from './Dogs';
const client = new ApolloClient({
uri: 'https://71z1g.sse.codesandbox.io/',
cache: new InMemoryCache()
});
const GET_DOGS = gql`
query GetDogs {
dogs {
id
breed
}
}
`;
function App() {
const [selectedDog, setSelectedDog] = useState<string | null>(null);
const onDogSelected: ChangeEventHandler<HTMLSelectElement> = ({ target }) => {
setSelectedDog(target.value);
};
return (
<ApolloProvider client={client}>
<div>
<h2>Building Query components 🚀</h2>
<Dogs onDogSelected={onDogSelected} GET_DOGS={GET_DOGS} />
</div>
</ApolloProvider>
);
}
export default App;
import { ChangeEventHandler, VFC } from 'react';
import { DocumentNode, useQuery } from '@apollo/client';
type Props = {
onDogSelected: ChangeEventHandler<HTMLSelectElement>;
GET_DOGS: DocumentNode;
};
export const Dogs: VFC<Props> = ({ onDogSelected, GET_DOGS }) => {
const { loading, error, data } = useQuery(GET_DOGS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error! {error.message}</p>;
return (
<select name="dog" onChange={onDogSelected}>
{data.dogs.map((dog: { id: string; breed: string }) => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
};
サンプル001■React + TypeScript: Apollo Client Queries 01
variables
で動的な変数値に応じてクエリからデータを取り出す
ドロップダウンメニューの選択に応じて、その犬種の画像を表示しましょう。そのためのコンポーネントがこのあと定めるDogPhoto
です。ルートモジュール(src/App.tsx
)に新たに加えるクエリ(GET_DOG_PHOTO
)は、選ばれた犬種(breed
)を変数$breed
に受け取って、その画像パス(displayImage
)を得る仕組みとなります。犬種名は状態変数(selectedDog
)に収められていますので、子コンポーネントのプロパティ(breed
)として、クエリ(GET_DOG_PHOTO
)とともに渡さなければなりません。
import { DogPhoto } from './DogPhoto';
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function App() {
return (
<ApolloProvider client={client}>
<div>
<h2>Building Query components 🚀</h2>
{selectedDog && (
<DogPhoto breed={selectedDog} GET_DOG_PHOTO={GET_DOG_PHOTO} />
)}
</div>
</ApolloProvider>
);
}
export default App;
画像表示のモジュール(src/DogPhoto.tsx
)は、プロパティ(breed
)に受け取った犬種名をuseQuery
フックの第2引数(オプションオブジェクト)にvariables
として、オブジェクトに収めて与えてください(コード002)。これで、プロパティ値が動的に変わったとき、値に応じたデータがクエリから得られるのです(「Variables」参照)。書き改めたルートモジュール(src/App.tsx
)の記述も併せて示しました。
コード002■プルダウンメニューの選択に応じて選んだ犬種の画像を表示する
import { DocumentNode, useQuery } from '@apollo/client';
import { VFC } from 'react';
type Props = {
breed: string;
GET_DOG_PHOTO: DocumentNode;
};
export const DogPhoto: VFC<Props> = ({ breed, GET_DOG_PHOTO }) => {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed }
});
if (loading) return null;
if (error) return <p>Error! {error}</p>;
return (
<div>
<img
src={data.dog.displayImage}
style={{ height: 100, width: 100 }}
alt="selected dog"
/>
</div>
);
};
import { ChangeEventHandler, useState } from 'react';
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
gql
} from '@apollo/client';
import { Dogs } from './Dogs';
import { DogPhoto } from './DogPhoto';
const client = new ApolloClient({
uri: 'https://71z1g.sse.codesandbox.io/',
cache: new InMemoryCache()
});
const GET_DOGS = gql`
query GetDogs {
dogs {
id
breed
}
}
`;
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function App() {
const [selectedDog, setSelectedDog] = useState<string | null>(null);
const onDogSelected: ChangeEventHandler<HTMLSelectElement> = ({ target }) => {
setSelectedDog(target.value);
};
return (
<ApolloProvider client={client}>
<div>
<h2>Building Query components 🚀</h2>
{selectedDog && (
<DogPhoto breed={selectedDog} GET_DOG_PHOTO={GET_DOG_PHOTO} />
)}
<Dogs onDogSelected={onDogSelected} GET_DOGS={GET_DOGS} />
</div>
</ApolloProvider>
);
}
export default App;
アプリケーションの動きは、CodeSandboxに公開したつぎのサンプル002でお確かめください。なお、Apollo Clientで読み込んだデータはキャッシュされます。たとえば、秋田犬(akita)を選んでからブルドッグ(bulldog)に切り替え、そのあと秋田犬に戻すと、画像はすぐに表示されるはずです。
サンプル002■React + TypeScript: Apollo Client Queries 02
キャッシュしたクエリの結果を更新する
キャッシュされたクエリのデータを、サーバーのデータで更新したいこともあるでしょう。Apollo Clientはふたつのやり方を提供しています。ひとつは、一定間隔でサーバーに問い合わせるpollInterval
です。useQuery
の第2引数(オプションオブジェクト)に加えて、時間をミリ秒で与えます。
export const DogPhoto: VFC<Props> = ({ breed, getDogPhoto }) => {
const { loading, error, data } = useQuery(getDogPhoto, {
pollInterval: 500
});
};
もうひとつは、あらかじめ決めた操作で呼び出すrefetch
関数により、クエリ結果を更新することです。関数はuseQuery
の戻り値から得られます。画像表示のモジュール(src/DogPhoto.tsx
)に新たに加えたボタン(<button>
)から呼び出すことにしました。ひとつの犬種に画像は複数用意されているようですので、ボタンを押すたびに新たな画像に切り替わるはずです。
export const DogPhoto: VFC<Props> = ({ breed, GET_DOG_PHOTO }) => {
// const { loading, error, data } = useQuery(
const { loading, error, data, refetch } = useQuery(
);
return (
<div>
<button onClick={() => refetch()}>Refetch!</button>
</div>
);
};
更新の読み込み状況をたしかめる
refetch
関数で画像が更新されるようにはなりました。けれど、データが読み込まれて再描画されるまで、画面はそのままです。読み込み中であることを、ユーザーに示すようにしましょう。ただし、クエリの結果はすでに得られていますので、loading
は役に立ちません。
ここで用いるのがnetworkStatus
です。useQuery
の戻り値から取り出します。ただし、第2引数のオプションオブジェクトでnotifyOnNetworkStatusChange
をtrue
に定めなければ使えません。更新しているときの値はNetworkStatus.refetch
です。
// import { DocumentNode, useQuery } from '@apollo/client';
import { DocumentNode, NetworkStatus, useQuery } from '@apollo/client';
export const DogPhoto: VFC<Props> = ({ breed, GET_DOG_PHOTO }) => {
// const { loading, error, data } = useQuery(
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
notifyOnNetworkStatusChange: true
}
);
if (networkStatus === NetworkStatus.refetch) return <p>Refetching!</p>;
};
これで、データの更新中はテキストが示されるようになりました。なお、NetworkStatus
はenum
で、値はつぎのような整数値です。それぞれについて、ソースコードのsrc/core/networkStatus.ts
にコメントが添えられていますのでご参照ください。
export enum NetworkStatus {
loading = 1,
setVariables = 2,
fetchMore = 3,
refetch = 4,
poll = 6,
ready = 7,
error = 8,
}
書き改めたモジュールsrc/DogPhoto.tsx
の記述はつぎのコード003のとおりです。公式サイトの「Queries」で紹介された作例をTypeScriptに対応させ、モジュール分けもできました。動きはCodeSandboxに公開した以下のサンプル003でお確かめください。
コード003■キャッシュしたクエリの画像データを更新する
import { VFC } from 'react';
import { DocumentNode, NetworkStatus, useQuery } from '@apollo/client';
type Props = {
breed: string;
GET_DOG_PHOTO: DocumentNode;
};
export const DogPhoto: VFC<Props> = ({ breed, GET_DOG_PHOTO }) => {
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
notifyOnNetworkStatusChange: true
}
);
if (networkStatus === NetworkStatus.refetch) return <p>Refetching!</p>;
if (loading) return null;
if (error) return <p>Error! {error}</p>;
return (
<div>
<div>
<img
src={data.dog.displayImage}
style={{ height: 100, width: 100 }}
alt="selected dog"
/>
</div>
<button onClick={() => refetch()}>Refetch!</button>
</div>
);
};
サンプル003■React + TypeScript: Apollo Client Queries 03
関連記事
「React + TypeScript: Apollo ClientのuseMutationフックでGraphQLのデータを書き替える」
-
公式サイトの「Executing a query」に掲げられているコード例は、コンポーネント
Dogs
がloading
とerror
の条件判定文から文字列を返しています。TypeScriptで型づけしたReact関数コンポーネントの戻り値は、JSXでないとエラーになってしまいますので、ご注意ください。 ↩