React + TypeScript: Apollo ClientのuseQueryフックでGraphQLのデータを読み込む

[Apollo Client](https://www.apollographql.com/docs/react/)はReactで使える状態管理ライブラリです。ローカルとリモートのデータをGraphQLで扱えます。本稿は、すでに公開した「[React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる](https://qiita.com/FumioNonaka/items/0c6b711627e3443ff73b)」の続編です。公式サイトの「[Queries](https://www.apollographql.com/docs/react/data/queries/)」で紹介された作例に、TypeScriptを採り入れ、アプリケーションは簡単にモジュール分けしてつくります。

# `useQuery`でデータを読み込む
お題とする公式作例は、CodeSandboxに公開されている「[Queries > Example app final](https://codesandbox.io/s/queries-example-app-final-nrlnl)」です。モジュールは`src/index.js`ひとつで、TypeScriptも使われていません。本稿では、Reactアプリケーションのひな形を[Create React App](https://ja.reactjs.org/docs/create-a-new-react-app.html#create-react-app)でつくりましょう。やり方については、「React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる」の「[Apollo Clientアプリケーションをつくる準備](https://qiita.com/FumioNonaka/items/0c6b711627e3443ff73b#apollo-client%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8B%E6%BA%96%E5%82%99)」をお読みください。

はじめの一歩は、以下のふたつのモジュール(`src/App.tsx`と`src/Dogs.tsx`)です(コード001)[^1]。[`useQuery`](https://www.apollographql.com/docs/react/api/react/hooks/#usequery)フックを用いて、GraphQLの犬種データをドロップダウンメニュー(要素`<select>`の子の`<option>`)に加えました(詳しくは、前出「React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる」参照)。まだ、メニューを選んでも何も起こりません。ご参考までに、サンプル001をCodeSandboxに公開しました。

なお、クエリ`GET_DOGS`に収めた`gql`に渡しているクエリ文字列先頭の`query GetDogs`は[オペレーション名](https://graphql.org/learn/queries/#operation-name)です。オペレーションタイプのキーワード`query`のあとにオペレーション名を添えます。省略しても構いません。けれど、加えることでコードはよりわかりやすくなるでしょう。

[^1]: 公式サイトの「[Executing a query](https://www.apollographql.com/docs/react/data/queries/#executing-a-query)」に掲げられているコード例は、コンポーネント`Dogs`が`loading`と`error`の条件判定文から文字列を返しています。TypeScriptで型づけしたReact関数コンポーネントの戻り値は、JSXでないとエラーになってしまいますので、ご注意ください。

#### コード001■ドロップダウンメニューにGraphQLのデータを読み込む
```react:src/App.tsx
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;
```

```react:src/Dogs.tsx
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
https://codesandbox.io/s/react-typescript-apollo-client-queries-01-tyyj61


# `variables`で動的な変数値に応じてクエリからデータを取り出す
ドロップダウンメニューの選択に応じて、その犬種の画像を表示しましょう。そのためのコンポーネントがこのあと定める`DogPhoto`です。ルートモジュール(`src/App.tsx`)に新たに加えるクエリ(`GET_DOG_PHOTO`)は、選ばれた犬種(`breed`)を変数`$breed`に受け取って、その画像パス(`displayImage`)を得る仕組みとなります。犬種名は状態変数(`selectedDog`)に収められていますので、子コンポーネントのプロパティ(`breed`)として、クエリ(`GET_DOG_PHOTO`)とともに渡さなければなりません。

```react:src/App.tsx
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`](https://www.apollographql.com/docs/react/api/react/hooks/#variables)として、オブジェクトに収めて与えてください(コード002)。これで、プロパティ値が動的に変わったとき、値に応じたデータがクエリから得られるのです(「[Variables](https://graphql.org/learn/queries/#variables)」参照)。書き改めたルートモジュール(`src/App.tsx`)の記述も併せて示しました。

#### コード002■プルダウンメニューの選択に応じて選んだ犬種の画像を表示する
```react:src/DogPhoto.tsx
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>
	);
};
```

```react:src/App.tsx
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
https://codesandbox.io/s/react-typescript-apollo-client-queries-02-uwjl1x


# キャッシュしたクエリの結果を更新する
キャッシュされたクエリのデータを、サーバーのデータで更新したいこともあるでしょう。Apollo Clientはふたつのやり方を提供しています。ひとつは、一定間隔でサーバーに問い合わせる[`pollInterval`](https://www.apollographql.com/docs/react/api/react/hooks/#pollinterval)です。`useQuery`の第2引数(オプションオブジェクト)に加えて、時間をミリ秒で与えます。

```react:src/DogPhoto.tsx
export const DogPhoto: VFC<Props> = ({ breed, getDogPhoto }) => {
	const { loading, error, data } = useQuery(getDogPhoto, {

		pollInterval: 500
	});

};
```

もうひとつは、あらかじめ決めた操作で呼び出す[`refetch`](https://www.apollographql.com/docs/react/api/react/hooks/#refetch)関数により、クエリ結果を更新することです。関数は`useQuery`の戻り値から得られます。画像表示のモジュール(`src/DogPhoto.tsx`)に新たに加えたボタン(`<button>`)から呼び出すことにしました。ひとつの犬種に画像は複数用意されているようですので、ボタンを押すたびに新たな画像に切り替わるはずです。

```react:src/DogPhoto.tsx
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`](https://www.apollographql.com/docs/react/api/react/hooks/#networkstatus)です。`useQuery`の戻り値から取り出します。ただし、第2引数のオプションオブジェクトで[`notifyOnNetworkStatusChange`](https://www.apollographql.com/docs/react/api/react/hooks/#notifyonnetworkstatuschange)を`true`に定めなければ使えません。更新しているときの値は[`NetworkStatus.refetch `](https://github.com/apollographql/apollo-client/blob/d96f4578f89b933c281bb775a39503f6cdb59ee8/src/core/networkStatus.ts#L24)です。

```react:src/DogPhoto.tsx
// 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`](https://github.com/apollographql/apollo-client/blob/main/src/core/networkStatus.ts#L1)にコメントが添えられていますのでご参照ください。

```react: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■キャッシュしたクエリの画像データを更新する
```react:src/DogPhoto.tsx
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
https://codesandbox.io/s/react-typescript-apollo-client-queries-03-dw1h09


### 関連記事
「**[React + TypeScript: Apollo ClientのuseMutationフックでGraphQLのデータを書き替える](https://qiita.com/FumioNonaka/items/4a1b474c16de46254e60)**」