LoginSignup
1

posted at

updated at

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

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でないとエラーになってしまいますので、ご注意ください。

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
What you can do with signing up
1