LoginSignup
4
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-03-16

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.tsxsrc/Dogs.tsx)です(コード001)1useQueryフックを用いて、GraphQLの犬種データをドロップダウンメニュー(要素<select>の子の<option>)に加えました(詳しくは、前出「React + TypeScript: Apollo ClientのGraphQLクエリを使ってみる」参照)。まだ、メニューを選んでも何も起こりません。ご参考までに、サンプル001をCodeSandboxに公開しました。

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

コード001■ドロップダウンメニューにGraphQLのデータを読み込む

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;
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

variablesで動的な変数値に応じてクエリからデータを取り出す

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

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

コード002■プルダウンメニューの選択に応じて選んだ犬種の画像を表示する

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>
	);
};
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

キャッシュしたクエリの結果を更新する

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

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

		pollInterval: 500
	});

};

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

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です。useQueryの戻り値から取り出します。ただし、第2引数のオプションオブジェクトでnotifyOnNetworkStatusChangetrueに定めなければ使えません。更新しているときの値はNetworkStatus.refetch です。

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>;

};

これで、データの更新中はテキストが示されるようになりました。なお、NetworkStatusenumで、値はつぎのような整数値です。それぞれについて、ソースコードのsrc/core/networkStatus.tsにコメントが添えられていますのでご参照ください。

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■キャッシュしたクエリの画像データを更新する

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

関連記事

React + TypeScript: Apollo ClientのuseMutationフックでGraphQLのデータを書き替える

  1. 公式サイトの「Executing a query」に掲げられているコード例は、コンポーネントDogsloadingerrorの条件判定文から文字列を返しています。TypeScriptで型づけしたReact関数コンポーネントの戻り値は、JSXでないとエラーになってしまいますので、ご注意ください。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1