ポケモンAPIを使ってGraphQLを今更覚える！

## 目的

有志の方が制作されている[GraphQL のポケモン API](https://github.com/lucasbento/graphql-pokemon)を使って
ポケモン検索アプリを作る過程で React + TypeScript + GraphQL を覚える！

## 前述

本記事で作ったアプリは GitHub で公開しているので、よかったら記事を一緒に確認してください

https://github.com/azuharu07/pokemon-graphql-practice

## 事前準備

### とりあえず React + TypeScript 環境を作る

```
npx create-react-app pokemon-graphql-practice --template typescript
```

たったのこれだけで React + TypeScript 環境が作成できる（神）

```
cd pokemon-graphql-practice
yarn start
```

不要なファイルは削除しましょう

-   src/App.css
-   src/App.text.tsx
-   src/logo.svg
-   src/setupTest.ts

App.tsx も不要な記述を消してとりあえずスッキリさせます

```js
function App() {
    return <div>pokemon-graphql-practice</div>
}
```

-   src/App.css

### graphql-codegen で型定義を作る

GraphQL の Schema 情報から型定義ファイルを作成してくれるすごいやつです

### [公式 Docs](https://www.graphql-code-generator.com/docs/getting-started/installation)の Installation を参考にインストールする

```
yarn add graphql
yarn add -D @graphql-codegen/cli
yarn add -D @graphql-codegen/schema-ast // schema.graphqlを自動で作ってくれる！
```

#### セッティングを行う

```
yarn graphql-codegen init
```

色々聞かれるけどとりあえず全部 Enter ですっ飛ばして OK です
script 名を聞かれるのでそこだけ`generate`と入力します
graphql-codegen を実行するのに`yarn generate`で実行できるようになります
codegen.yml が作成されるので、以下のように修正します

```yml
schema: "https://graphql-pokemon2.vercel.app" # GraphiQLを指定
generates:
    ./src/@types/types.d.ts:
        plugins:
            - "typescript"
            - "typescript-operations" # fqueryやmutationで使用するOperationとかの型定義まで作ってくれる
    ./schema.graphql: # これ以下を記述するとschema.graphqlをDocumentから作ってくれる（ｽｺﾞｲ）
        plugins:
            - schema-ast
```

ここまで来たらあとは実行するのみです！

```
yarn generate
```

以下のファイルが作成されます

-   ./src/@types/types.d.ts
-   ./schema.graphql

### Apollo をインストール

[Apollo](https://www.apollographql.com/)はフロントエンドから簡単に GraphQL を操作できるようにするライブラリです

```
yarn add @apollo/client
```

## 実装

ここまで来たらもう準備万端！
早速実装に入りましょう！！！

...と言いたいところですが、その前に graphql-pokemon が提供している API 仕様を確認しましょう

### Pikachu の番号/名前/画像 url を取得する Query を試す

[GraphiQL](https://graphql-pokemon2.vercel.app/)を開き、右上の Docs を開きます（Docs の見方は省略します）

query:Query をクリックすると、query と pokemons と pokemon の Fields があることがわかります

![スクリーンショット 2021-05-05 18.35.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/255c3930-9891-d65a-0193-a73b2a7fbe5d.png)

今回 Pikachu のデータを取得したいので、pokemon を見てみると、`pokemon(id: String,name: String): Pokemon`とありますね

String 型の id または name を引数として渡すことで、Pokemon 型のレスポンスを返却することがわかります

では Pokemon 型の詳細を見てみましょう

![スクリーンショット 2021-05-05 18.35.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/8558e25e-5ae7-d113-0688-c3e84c56b9e8.png)

Pokemon 型が持つ Field が表示されました
いろんなデータがありますがとりあえず今回必要なのは

-   number
-   name
-   image

ですね
これを元に左側のエディタで Query を作成してみましょう！

![スクリーンショット 2021-05-05 18.41.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/5f4d2c05-55f1-e8d9-e1f7-2d244d116942.png)

serachPikachu Query を作りました
これを実行すると pikachu のデータが取れることがわかります

この"data"から始まる JSON が、クライアント側で Query を実行した時のレスポンスとして返されます

GraphiQL 超便利！！！

さてこれを実際にアプリで実行してみましょう！

### Apollo の準備

src/graphql/client.ts を作成し、そこに Apollo を使うためのクライアントを用意してあげます
ApolloClient を初期化します

```src/graphql/client.ts
import { ApolloClient, InMemoryCache } from "@apollo/client"

const apolloClient = new ApolloClient({
    uri: "https://graphql-pokemon2.vercel.app/",
    cache: new InMemoryCache(),
})

export { apolloClient }
```

uri には GraphiQL を、cache には InMemoryCache のインスタンスを渡します

cache を設定することで同じクエリが発行された場合、自動的にキャッシュから結果を返却してくれるので、パフォーマンスがよくなるみたいです
とりあえず入れておいて損は無いかと思います

App.ts に ApolloProvider と先ほど初期化した AppoloClient をインポートして、ApolloProvider でアプリ全体を囲ってあげます

```tsx
import { ApolloProvider } from "@apollo/client"
import { apolloClient } from "./graphql/client"

function App() {
    return (
        <ApolloProvider client={apolloClient}> // 追加
            <div>pokemon-graphql-practice</div>
        </ApolloProvider> // 追加
    )
}
```

これでこの ApolloProvider に囲われてるコンポーネント内で、GraphQL の query や mutation を実行することができるようになります

### 結果を表示しよう

GraphQL API を実行して結果を表示するコンポーネントを作っていきます

src/graphql/queries.ts に先ほど GraphQL で確認した Query を記載しておきましょう

gql にテンプレートリテラルで Query を書くといい感じにリクエストを作ってくれるみたいです

```src/graphql/queries.ts
import gql from "graphql-tag"

export const searchPikachu = gql`
    query searchPikachu {
        pokemon(name: "pikachu") {
            number
            name
            image
        }
    }
`
```

src/components/SearchResultField.tsx を作って、Query を実行し結果を表示するコンポーネントを作ります

```src/components/SearchResultField.tsx
import { useQuery } from "@apollo/client"
import { Query } from "../@types/types"
import { searchPikachu } from "../graphql/queries"

const SearchResultField = () => {
    const { loading, error, data } = useQuery<Query>(searchPikachu)

    if (loading) return <>"Loading..."</>
    if (error) return <>`Error! ${error.message}`</>

    return (
        <div>
            <div>No: {data?.pokemon?.number}</div>
            <div>Name: {data?.pokemon?.name}</div>
            {data?.pokemon?.image ? (
                <img src={data?.pokemon?.image} alt={data?.pokemon?.name ?? ""} />
            ) : (
                <div>no image.</div>
            )}
        </div>
    )
}
export { SearchResultField }
```

useQuery にジェネリクスとして、data の型を渡すことができます
@types から Query をとってきて渡すことで、ちゃんと data が Query 型だと VSCode が判定してくれました
当初の想定では data は Pokemon 型になると思っていましたが、data は GraphiQL での結果の JSON （以下）のように pokemon キーが一番上にあったので、data は Query 型にしました

```
{
  "data": {
    "pokemon": {
      ...
    }
  }
}
```

簡単に説明していきます

#### useQuery

```tsx
import { useQuery } from "@apollo/client"
const { loading, error, data } = useQuery<Query>(searchPikachu)
```

Apollo が用意してくれている Hooks です
通信の状況に応じて loading,error,data の値が変わるのでとても便利

#### ハンドリング

```tsx
if (loading) return <>Loading...</>
if (error) return <>Error! {error.message}</>
```

loading が true の場合はローディング中の文字、エラー発生時にはメッセージを返却するようにしています

`<></>`これはフラグメントと行ってとりあえず囲みたい時に使います　とても便利

#### データの表示

テストなので特別なハンドリングとかはせず、?.でごり押してます
img だけ src と alt に undifind を渡せなかったので undifind の場合 no image.　が表示されるようにしました

```tsx
return (
    <div>
        <div>No: {data?.pokemon?.number}</div>
        <div>Name: {data?.pokemon?.name}</div>
        {data?.pokemon?.image ? (
            <img src={data?.pokemon?.image} alt={data?.pokemon?.name ?? ""} />
        ) : (
            <div>no image.</div>
        )}
    </div>
)
```

ここまででPikachuのデータが表示されるようになりました

![スクリーンショット 2021-05-06 1.25.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/6b2fb449-f2db-f3ee-0605-a38ff1d6f9b8.png)

### 動的にポケモンの名前が動的に設定できるようにしよう

ここまでで結果を取得、表示ができました
あとは Pikachu 以外のポケモンも調べられるようにしたいので、検索フォームを作って動的にポケモンを検索できるようにします

まずは GraphiQL で変数を使った Query を作成してみます

#### 変数を使った Query

Query に変数を渡すには以下のように記載します

![スクリーンショット 2021-05-05 21.46.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/8ad8c02e-f06d-b538-feb2-7158f7c52681.png)
-2a9914945eb4.png)

左下の QUERY VARIABLES で変数に Charizard を入れて、それを左上の query で使用しています
検索結果が 006 Charizard になったのがわかりますね

src/graphql/queries.ts に追加しておきましょう

```src/graphql/queries.ts
export const searchPokemon = gql`
    query searchPokemon($name: String) {
        pokemon(name: $name) {
            number
            name
            image
        }
    }
`
```

#### 検索フォームを作成

src/components/SearchForm.tsx を作成し、検索フォームを作っていきます

```src/components/SearchForm.tsx
import { FormEvent, useRef } from "react"

type PropType = {
    setpokemonName: React.Dispatch<React.SetStateAction<string>>
}

const SearchForm: React.FC<PropType> = ({ setpokemonName }) => {
    const ref = useRef<HTMLInputElement>(null)

    const submitHandler = (e: FormEvent) => {
        e.preventDefault()
        if (ref !== null && ref.current !== null) {
            setpokemonName(ref.current.value)
        }
    }

    return (
        <form onSubmit={submitHandler}>
            <input type="text" ref={ref} placeholder="input Pokemon's name" />
            <input type="submit" value="Search"></input>
        </form>
    )
}

export { SearchForm }
```

src/App.tsx もこれに伴い修正

```src/App.tsx
function App() {
    const [pokemonName, setpokemonName] = useState("")

    return (
        <> // 追加
            <SearchForm setpokemonName={setpokemonName}></ SearchForm> // 変更
            <ApolloProvider client={apolloClient}>
                <SearchResultField></SearchResultField>
            </ApolloProvider>
        </> // 追加
    )
}
```

React の解説記事では無いので詳細な説明は省きますが、

-   input:text を useRef Hooks で定義
-   form の onSubmit で ref を参照し、value を取得している
-   App.tsx で useState で作成した pokemonName のセッター？を渡して、onSubmit でそこに value を突っ込む

って感じのフォームにしています
Submit ボタンを押した時、テキストボックスの値が App.tsx の pokemonName に、入るようになりました

#### 検索条件を動的な値に設定する

さて、検索フォームから受け取った pokemonName を使って検索結果を表示しましょう

```src/components/SearchResultField.tsx
// ↓追加
type PropType = {
    pokemonName: string
}

const SearchResultField: React.FC<PropType> = ({  pokemonName }) => { // 変更
    const { loading, error, data } = useQuery<Query> (searchPokemon, { variables: { name: pokemonName } }) // 変更

    if (!pokemonName) return <></> // 追加
    if (loading) return <>Loading...</>
    if (error) return <>Error! {error.message}</>
    if (!data || !data.pokemon) return <>No Data.</> // 追加

    // ↓変更
    return (
        <>
            <div>No: {data.pokemon.number}</div>
            <div>Name: {data.pokemon.name}</div>
            {data.pokemon.image ? <img src={data.pokemon.image} alt={data.pokemon.name ?? ""} /> : <div>no image.</div>}
        </>
    )
}
```

`useQuery<Query> (searchPokemon, { variables: { name: pokemonName } })`
useQuery に searchPokemon を第一引数で渡し、第二引数に variables を保持した Object を渡しています
この variables には先ほど GraphiQL の左下での QUERY VARIABLES で確認した物と同じ型のオブジェクトを渡します
useQuery は変数として渡したパラメータが変更された時に再度 Query を実行してくれるので、これで検索条件を可変にすることができました

最後に App.tsx から pokemonName を渡します
ついでに pokemonName が存在しない場合は何も表示しないようにしました

```App.tsx
<ApolloProvider client={apolloClient}>
    {pokemonName && <SearchResultField pokemonName={pokemonName}> // 変更</SearchResultField>}
</ApolloProvider>
```

これで Submit が押される度に検索結果が更新されるようになりました！

![pokemon-qiita-mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/04ed0ff6-45d8-c2bf-9c88-788765d08c9f.gif)

## まとめ

React + TypeScript + GraphQL でポケモンの画像検索ができる簡易的なアプリを作成しました

大好きなポケモンでGraphQLを勉強出来て、[graphql-pokemon](https://github.com/lucasbento/graphql-pokemon)の作者には感謝してもしきれません
が、欲を言うなら全ポケモンに対応して欲しい・・・あと日本語・・・

GraphQL の勉強のついでに作ってみましたが、GraphiQL 最高すぎるし、graphql-code-generator で型定義ファイルが作れるの天才だし、Apollo がとても使いやすくて、簡単でした

GraphiQL のお陰で REST API より簡単にお試しできるので、とてもとっつきやすかったです
ただ GraphQL じゃないと出来ないことっていうのがそこまで内容に見受けられました
最近流行りの gRPC とかだと REST ではできないことが出来たりするみたいです
とても気に入ったので GraphQL には是非これから流行って欲しいですが、gRPC も勉強して流れに乗り遅れないようにしないといけないなと思いました・・・

今回は query しか使っていませんが（graphql-pokemon には query しか用意されていません）
GitHub API とかには mutation もあるので mutation の記事もそのうち書きたいです
バックエンドの GraphQL の書き方も勉強しないと・・・

## 参考

[graphql-pokemon](https://github.com/lucasbento/graphql-pokemon)

[graphql-pokemon の GraphiQL](https://graphql-pokemon2.vercel.app)

[graphql-code-generator 公式 Docs](https://www.graphql-code-generator.com/docs/getting-started/installation)

[GraphQL Code Generator の使い方。〜GraphQL の Schema から TS の型定義を自動生成する〜](https://qiita.com/ryo2132/items/601ec3c085fcd99658e5#%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%AB)

[Apollo Docs](https://www.apollographql.com/docs/)