Apollo Client + React 入門

こんにちは。

この記事ではGraphQLのクライアントであるApollo Clientの入門をReactをお供に書きます。日本ではVueが一番人口多いんじゃないかという気がしているのですが、Vueが人気過ぎてReactも盛り上げねば！という謎の使命感を感じているのでReactで書くことにしました。

ただ、Apollo Clientは他のUIライブラリでも利用可能で、また考え方とかは一緒なのでReact使わないという方もぜひ読んでいってください。

話さないこと
------------------------------
「入門」とついてるのでちゃんとGraphQLとはなんぞや、Apollo Clientとはなんぞやという点に対して言及すべきなのですが、これについては他の記事で書いたのでそちらをご覧ください。
[世のフロントエンドエンジニア達にApollo Clientを布教したい](https://qiita.com/seya/items/26c8a0dc549a10efcdf8)

バージョン情報
------------------------------
本記事内で使われている主要なパッケージのバージョンを記載します。

|パッケージ名|バージョン|
|:--|:--|
|apollo-boost|0.1.7|
|graphql|0.13.2|
|graphql-tag|2.9.2|
|react|16.4.0|
|react-apollo|2.1.4|
|react-dom|16.4.0|
|react-scripts|1.1.4|

セットアップ
------------------------------

### create-react-app でプロジェクトを作成する
まずは手っ取り早く `create-react-app` でプロジェクトを作成してしまいましょう。

`npx` で実行するか、グローバルインストールしたい場合は以下のコマンドを実行してください。

```sh
npm install -g create-react-app
```

そして任意の場所にプロジェクトを作成してください。

```sh
create-react-app hello-apollo
```

### GitHub APIセットアップ
大体GraphQLクライアントのチュートリアルはGitHub APIが利用されるので、慣習に従いこの記事でも使っていきます。もしGitHubアカウントを持っていない方がいらっしゃれば作るところから頑張りましょう。

GitHubにログインしている状態でこちらのリンクに行ってください。
[https://github.com/settings/tokens](https://github.com/settings/tokens)

* `Generate New Tokens` というボタンを押してください
* パスワード入力後チェックボックスがズラッと出てくると思うので、適当に `Token description` にテキスト入力するのと、`public_repo` のところにチェックしてください
  * 他にチェックボックスをいくらつけても構いませんが、本チュートリアルで必要なのは`public_repo`のみです。

### envファイルの設置
作成したトークンはこれから作成するアプリケーション内からGitHub APIにアクセスするために必要ですがgitで管理はしたくないので `.env`ファイルに入れます。
先ほど作成したプロジェクト内に `.env` ファイルを作成してください。こんな感じ。

<img width="206" alt="スクリーンショット 2018-06-03 20.43.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/150569/3e82a58a-8420-9dda-84f2-5d3921a4ce56.png">


そしてその中に以下のように記述してください。

```
REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN=ここに先ほど作成したトークンをコピペしてください
```

これによりアプリケーション内のコードからは以下のようにトークンを扱うことができるようになります。

```js
process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
```

この時ご注意いただきたいのが、`create-react-app` で作ったプロジェクト内でenv変数を扱う際には `REACT_APP_` というプレフィックスが必要です。
それ以降は自由な名前をつけていただければと思いますが、そこだけ守りましょう。

### 必要なpackageのインストール

最後に、Apollo Clientを動かすために必要なパッケージをインストールします。
下記コマンドを実行してください。

```
npm install apollo-boost react-apollo graphql-tag graphql --save
```

一応各パッケージの簡易な解説を。

* [`apollo-boost`](https://github.com/apollographql/apollo-client/tree/master/packages/apollo-boost)
  * その名の通り「とりあえずApolloで動かしたいんや！！」というあなたをブーストしてくれるパッケージです。
  * `apollo-cache-inmemory` や `apollo-link-http` などApollo Clientを使う際に基本使うことになるであろうパッケージたちが一緒にインストールされます。詳しい中身を知りたい方は[README](https://github.com/apollographql/apollo-client/tree/master/packages/apollo-boost)をどうぞ。
* [`react-apollo`](https://github.com/apollographql/react-apollo)
  * ReactとGraphQLクエリの繋ぎこみをサポートしてくれるライブラリです。
* [`graphql-tag`](https://github.com/apollographql/graphql-tag)
  * GraphQLクエリをテンプレートリテラルで書いたものをパースしてくれる便利なライブラリです。
* [`graphql`](https://github.com/apollographql/graphql-js)
  * `graphql-tag`のpeerDependencyとして必要です。他にも色々機能あるパッケージなんですが、今回入れる目的はそれ。

とりあえず値を取得してみる
------------------------------

長いセットアップが終わったところで、Apollo Clientと戯れて行きましょう！！！！
`index.js` をお好きなテキストエディタで開いてください。。

まずはGraphQLクライアントを作成します。

```js:index.js
import ApolloClient from "apollo-boost";

const client = new ApolloClient({
  uri: 'https://api.github.com/graphql',
  request: operation => {
    operation.setContext({
      headers: {
        authorization: `Bearer ${
          process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
        }`
      }
    });
  }
});
```

次にクエリを足します。
クエリの中身は「`apollographql` という組織のレポジトリ5件の "ID、名前、URL、あなたがこのレポジトリを`star`してるかどうか、レポジトリのStar数" 」を取得しています。他にどんなフィールドがあるのかは[GitHub APIのドキュメント](https://developer.github.com/v4/object/organization/)を漁ってみてください。

```js:index.js
import gql from "graphql-tag";

const query = gql`
  {
    organization(login: "apollographql") {
      repositories(first: 5) {
        nodes {
          id
          name
          url
          viewerHasStarred
          stargazers {
            totalCount
          }
        }
      }
    }
  }
`

client
  .query({
    query
  })
  .then(result => console.log(result));
```

`index.js` の全体はこんな感じになっているはずです。

```jsx:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import ApolloClient from "apollo-boost";
import gql from "graphql-tag";

const client = new ApolloClient({
  uri: 'https://api.github.com/graphql',
  request: operation => {
    operation.setContext({
      headers: {
        authorization: `Bearer ${
          process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
        }`
      }
    });
  }
});

const query = gql`
  {
    organization(login: "apollographql") {
      repositories(first: 5) {
        nodes {
          id
          name
          url
          viewerHasStarred
          stargazers {
            totalCount
          }
        }
      }
    }
  }
`

client
  .query({
    query
  })
  .then(result => console.log(result));

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
registerServiceWorker();

```

できたら `npm run start` もしくは `yarn start` でアプリケーションを立ち上げて、コンソールを開いてみましょう！
下記のように表示されていたら成功です。

<img width="656" alt="スクリーンショット 2018-06-03 11.19.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/150569/2340cf74-a207-6c4b-45ca-cbb79d1af720.png">

React とのつなぎこみ Query コンポーネント
------------------------------
次にReactコンポーネントと組み合わせてみましょう。

まずは`index.js` 内の `<App>` をレンダーしているところを `Apollo Provider` を囲みましょう。これにより下位のコンポーネントのどこでも先ほど設定したGraphQLクライアントに対して参照が持てます。

```jsx:index.js
import { ApolloProvider } from 'react-apollo';

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);
```

そして`App` コンポーネントはレポジトリのリストを表示するようにします。
`App.js`を開いて以下のように書き直してみてください。

```jsx:App.js
import React, { Component } from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

const query = gql`
  {
    organization(login: "apollographql") {
      repositories(first: 5, isFork: false) {
        nodes {
          id
          name
          url
          viewerHasStarred
          stargazers {
            totalCount
          }
        }
      }
    }
  }
`;

const App = () => (
  <Query query={query}>
    {({ loading, data }) => {
      if (loading) return <p>Loading...</p>;

      const repositories = data.organization.repositories.nodes;

      return (
        <ul>
          {repositories.map(repo => (
            <li key={repo.id}>
              <a href={repo.url}>{repo.name}</a>
              <button>{repo.stargazers.totalCount} Star</button>
            </li>
          ))}
        </ul>
      );
    }}
  </Query>
);

export default App;
```

新しい登場人物がいますね。「Queryコンポーネント」さんです。QueryコンポーネントはRender Propパターンで実装されており(よく見る `<Query render={...}>`の形ではなく、childrenに関数入ってくる前提で `this.props.children(queryResult)` と実装していた。勉強になった。)、Queryコンポーネント内に関数を書いてあげると中身にQueryの結果が入ってきます。

それでは画面を開いて、下記のように表示されていれば成功です。

<img width="551" alt="スクリーンショット 2018-06-03 11.48.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/150569/aba8c45d-0a7e-a618-b71a-57b7c5825e6e.png">

CSSがないので残念な見た目ではありますが、ちゃんとGithub APIから取得したデータが表示されました。

### コラム: Render Prop vs. HOC

Queryコンポーネントではなく `graphql` オブジェクトを `react-apollo` から使うことによってHOCで書くことができます。
下記が先ほどと同じことをHOCで書いた実装例です。

```jsx:App.js
import React from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

const query = gql`さっきのクエリ`;

const App = ({ data: { loading, organization } }) => {
  if (loading) return <p>Loading...</p>;

  const repositories = organization.repositories.nodes;

  return (
    <ul>
      {repositories.map(repo => (
        <li key={repo.id}>
          <a href={repo.url}>{repo.name}</a>
          <button>{repo.stargazers.totalCount} Star</button>
        </li>
      ))}
    </ul>
  );
};

export default graphql(query)(App);
```

個人的にはViewの部分とデータの部分がきっちり分かれていて、Apollo ClientとReactの繋ぎこみという文脈ではHOCの方が好きです。
ただ色んな状況に対応しやすいという意味ではやはりRender Propの方が上手で、例えば

* クエリの中身をコンポーネントに渡されるpropsに応じて動的に書き換えたい
* あるクエリが他のクエリに依存している
  * Render Propなら `<Query><Query>{...}</Query></Query>` とするだけで実現できる

のような例だとRender Propの方が全然少ないコードで書けるなあという印象です。
ただHOCも知ってて損はない書き方なので、頭に入れといて状況に応じて使い分けましょう。

エラーハンドリング
------------------------------

次にクエリを送った際のエラーハンドリングをしてみましょう。

### Queryコンポーネント内で Errorハンドリングする
コンポーネント内で扱うのは非常に簡単で、`error` という引数をとってあげればよいだけです。

```jsx
const App = () => (
  <Query query={query}>
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>{error.toString()}</p>;

      /* renderする処理 */
    }}
  </Query>
);
```

クエリの中身をデタラメにすればエラー起こせるので試しにやってみましょう。

### apollo-link-errorのonError で扱う
もう一つ方法があって`apollo-link-error`の`onError`を使ってGraphQLクライアントにエラーハンドリングさせることができます。

```js
const client = new ApolloClient({
  uri: 'https://api.github.com/graphql',
  request: // hogehoge
  },
  onError: ({ graphQLErrors, networkError }) => {
    if (graphQLErrors) {
      // graphQLErrors固有の処理
    }
    if (networkError) {
      // networkError固有の処理
    }
  }
});
```

これによってコンポーネント毎に固有の処理を書く必要がなくなり、一括してエラーハンドリングの方法を指定することができます。

コラム: terminating link to non-terminatinglink
------------------------------
ちなみにapollo-boost抜きであれば以下のように`Link`を組み合わせて書くことになります。

```js
const httpLink = new HttpLink({
  uri: 'https://api.github.com/graphql',
  headers: {
    authorization: `Bearer ${
      process.env.REACT_APP_GITHUB_PERSONAL_ACCESS_TOKEN
    }`
  }
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    // do something with graphql error
  }

  if (networkError) {
    // do something with network error
  }
});

const link = ApolloLink.from([errorLink, httpLink]);
const cache = new InMemoryCache();

const client = new ApolloClient({
  link,
  cache
});
```
Linkは連ねて使うことができ、Reduxのmiddlewareのようなものとイメージすると分かりやすいかもしれません。Apollo Clientにおいて重要な役割を持つ `Link` ですが、Apollo界ではLinkを二つに分類します。それが `terminating link` と `non-terminating link` です。

`terminating link`はその名の通り最後に位置するLinkで、今回の例でいうと`HttpLink`です。上記のコード例では`ApolloLink.from([...])`と複数のLinkの配列を作っていますが、terminating linkは必ず最後に書かなければいけません。ネットワークリクエストを生じるようなものがこの分類にされ、他には`WebSocketLink`などがあります。

対して`non-terminating link`は`terminating link`に機能を足すようなものを指します。
今回の例でいうと `ErrorLink` ですね。

他にも色んな種類のLinkがあるので興味があればこちらをご覧ください。
[Apollo: Available Links](https://www.apollographql.com/docs/link/#linkslist)

Mutation コンポーネント
------------------------------
最後にデータ変更の操作 `Mutation` を学びましょう。
先ほどリスト表示したレポジトリたちに対してStarしたりUnstarしたりできるようにします。

まずはクエリの定義を。

参考
* https://developer.github.com/v4/mutation/addstar/
* https://developer.github.com/v4/mutation/removestar/

```jsx:App.js
const ADD_STAR_REPOSITORY = gql`
  mutation($id: ID!) {
    addStar(input: { starrableId: $id }) {
      starrable {
        id
        viewerHasStarred
      }
    }
  }
`;

const REMOVE_STAR_REPOSITORY = gql`
  mutation($id: ID!) {
    removeStar(input: { starrableId: $id }) {
      starrable {
        id
        viewerHasStarred
      }
    }
  }
`;
```

そして `Mutation` コンポーネントを定義します。
例によってRender Propパターンで実装されておりますが、今回違うのはMutationのトリガーとなる関数が第一引数に入ってくることです。これをbuttonのonClickとかにセットして実行できるようにします。

```jsx:App.js
import {Query, Mutation} from 'react-apollo';

const App = () => (
  <Query query={query}>
    {({ loading, data }) => {
      if (loading) return <p>Loading...</p>;

      const repositories = data.organization.repositories.nodes;

      return (
        <ul>
          {repositories.map(repo => (
            <li key={repo.id}>
              <a href={repo.url}>{repo.name}</a>
              <button>{repo.stargazers.totalCount} Stars</button>

              // 既にstarしてあるかどうかで出し分け
              {!repo.viewerHasStarred ? (
                <Mutation
                  mutation={ADD_STAR_REPOSITORY}
                  variables={{ id: repo.id }}
                >
                  {(addStar, { data, loading, error }) => (
                    <button onClick={addStar}>star</button>
                  )}
                </Mutation>
              ) : (
                <Mutation
                  mutation={REMOVE_STAR_REPOSITORY}
                  variables={{ id: repo.id }}
                >
                  {(removeStar, { data, loading, error }) => (
                    <button onClick={removeStar}>unstar</button>
                  )}
                </Mutation>
              )}
            </li>
          ))}
        </ul>
      );
    }}
  </Query>
);
```

こんな感じに表示されると思うので、あとは思う存分 star したり unstar したりしましょう。(せっかくなので遊び終わったあとはStarしといたままにしましょう💫)

<img width="357" alt="スクリーンショット 2018-06-03 13.47.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/150569/747bf867-b40c-94ec-a54d-0bfe2afb6643.png">

まとめ
------------------------------

本チュートリアルは以上です。これを機にApollo仲間が増えると嬉しいです。
本当は `apollo-link-state` や `apollo-cache-persist` などについても語りたかったのですが、さっさと記事を出したい欲が勝ってしまったので今回はベーシックなケースのみで。また記事を書くと思うのでよければそちらもお読みください。

それでは、よきコーディングを！