【React】ApolloClientを使ってGraphQLサーバにアクセスする

## 概要

- ReactからGraphQLサーバにアクセスするためのClientライブラリとして[ApolloClient](https://www.apollographql.com/docs/react/)があります
- 数年前にさわったことがありましたがHooks対応してかなり使いやすくなっていたのでセットアップとQuery/Mutationの叩き方を簡単に紹介します
- ※ApolloClientは執筆日時点(2020/3/28)でbeta版であるv3系を使っています

## 事前準備

- Reactの追加

```bash
yarn add react react-dom
```

- index.htmlの作成

```html:index.html
<div id="root"></div>
<script src="index.js"></script>
```

- index.jsの作成

```jsx:index.js
import React from 'react';
import { render } from 'react-dom';
import App from './src/App';

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

- src/App.jsの作成

```jsx:src/App.js
import React from 'react';

function App() {
  return (
    <div>
      <h1>Hello</h1>
    </div>
  );
}

export default App;
```

- 起動
    - 今回は手っ取り早く試すためParcelを使います

```bash
npx parcel-bundler index.html
```

- http://localhost:1234 で起動します

### ApolloClientのセットアップ

- ここからが本題です
- まずはライブラリを追加します

```bash
yarn add @apollo/client graphql
```

- 次にGraphQLサーバのアクセス先などを設定します
    - ApolloClientの公式ページに登場するTODOアプリのGraphQLサーバを使っています
    - ブラウザで https://plp0mopxq.sse.codesandbox.io/ にアクセスするとPlaygroundでスキーマなどを確認できます

```js:src/graphql/client.js
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';

export default new ApolloClient({
  cache: new InMemoryCache(),
  link: new HttpLink({
    uri: 'https://plp0mopxq.sse.codesandbox.io/graphql',
  }),
});

```

- App.jsに反映します

```jsx:src/App.js
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './graphql/client';

function App() {
  return (
    <ApolloProvider client={client}>
      <h1>Hello</h1>
    </ApolloProvider>
  );
}

export default App;
```

- これでセットアップ完了です

## Queryを実行する

- 実行するQueryを定義するファイルを作成します
    - Todoの全量を取得する`GET_TODOS`と、指定したIDのTodoを1件取得する`GET_TODO`の2つを定義します
    - Query実行時に引数を渡す場合は`GET_TODO`で定義しているような書き方をします

```js:src/graphql/query.js
import { gql } from '@apollo/client';

// 全件取得
export const GET_TODOS = gql`
  query getTodos {
    todos {
      id
      type
    }
  }
`;

// 指定したIDのTODOを1件取得
export const GET_TODO = gql`
  query getTodo($id: String!) {
    todo(id: $id) {
      id
      type
    }
  }
`;
```

### useQuery

- コンポーネントから`GET_TODOS`のQueryを呼び出します
- `useQuery`を使うとコンポーネントが生成された時にQueryが実行されます
    - 今回のように一覧を取得して表示するようなケースで使うイメージですね
    - 今回実行する`GET_TODOS`は引数なしですが、ありの場合は`useQuery`の第2引数で指定することができます
        - `useQuery(GET_TODOS, { variables: { xx: 'xx' } })`

```jsx:src/components/TodoList.js
import React from 'react';
import { useQuery } from '@apollo/client';
import { GET_TODOS } from '../graphql/query';

function TodoList() {
  // Queryを実行
  const { loading, error, data } = useQuery(GET_TODOS);

  if (loading) return <p>...loading</p>;
  if (error) return <p>{error.message}</p>;

  return (
    <div>
      <h2>TodoList</h2>
      {data.todos.map(todo => (
        <p key={todo.id}>{todo.id}: {todo.type}</p>
      ))}
    </div>
  );
}

export default TodoList;
```

- App.jsに反映します

```jsx:src/App.js
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './graphql/client';
// importを追加
import TodoList from './components/TodoList';

function App() {
  return (
    <ApolloProvider client={client}>
      <h1>Hello</h1>
      {/* コンポーネントを追加 */}
      <TodoList />
    </ApolloProvider>
  );
}

export default App;
```

![todolist.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/a0299015-a688-ba89-ba27-e66f236868a3.gif)

### useLazyQuery

- IDを指定してTodoを1件取得する`GET_TODO`をコンポーネントから呼び出します
- ユーザのアクションなどをきっかけにQueryを実行したい時は`useLazyQuery`を使います
- Query実行時の引数は`{ variables: {...} }`といった形式で`variables`は固定文言でその中に必要な値をセットします

```jsx:src/components/Todo.js
import React, { useRef } from 'react';
import { useLazyQuery } from '@apollo/client';
import { GET_TODO } from '../graphql/query';

function Todo() {
  const inputRef = useRef(null);
  // getTodoを呼び出すとQueryが実行される
  const [getTodo, { loading, error, data }] = useLazyQuery(GET_TODO);

  return (
    <div>
      <h2>Todo</h2>
      <div>
        <p>
          <input ref={inputRef} />
          {/* clickされたらgetTodoを実行する */}
          <button onClick={() => getTodo({ variables: { id: inputRef.current.value } })}>
            GET
          </button>
        </p>
        {loading && <p>...loading</p>}
        {error && <p>{error.message}</p>}
        {data && <p>ID: {data.todo.id}, Todo: {data.todo.type}</p>}
      </div>
    </div>
  );
}

export default Todo;
```

- App.jsに反映します

```jsx:src/App.js
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './graphql/client';
import TodoList from './components/TodoList';
// importを追加
import Todo from './components/Todo';

function App() {
  return (
    <ApolloProvider client={client}>
      <h1>Hello</h1>
      <TodoList />
      {/* コンポーネントを追加 */}
      <Todo />
    </ApolloProvider>
  );
}

export default App;
```

![todo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/71afd82f-b44a-7692-7f4a-9662d5edbd38.gif)

## Mutationを実行する

- 実行するMutationを定義するファイルを作成します
    - Todoを追加する`ADD_TODO`を定義します
    - 引数の書き方はQueryの時と同じです

```js:src/query/mutation.js
import { gql } from '@apollo/client';

export const ADD_TODO = gql`
  mutation AddTodo($type: String!) {
    addPlayer(type: $type) {
      id
      type
    }
  }
`;
```

### useMutation

- コンポーネントからMutationを実行してみます
- `useMutation`から取得できる`addTodo`を呼び出すとMutationを実行できます

```jsx:src/components/AddTodo.js
import React, { useRef } from 'react';
import { useMutation } from '@apollo/client';
import { ADD_TODO } from '../graphql/mutation';

function AddTodo() {
  const inputRef = useRef(null);
  const [addTodo, { loading, error }] = useMutation(ADD_TODO);

  return (
    <div>
      <h2>AddTodo</h2>
      {loading && <p>...loading</p>}
      {error && <p>{error.message}</p>}
      <p>
        <input ref={inputRef} />
        {/* クリックしたらaddTodoを実行する */}
        <button onClick={() => addTodo({ variables: { type: inputRef.current.value } })}>
          ADD
        </button>
      </p>
    </div>
  );
}

export default AddTodo;
```

- App.jsに反映します

```jsx:src/App.js
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import client from './graphql/client';
import TodoList from './components/TodoList';
import Todo from './components/Todo';
// importを追加
import AddTodo from './components/AddTodo';

function App() {
  return (
    <ApolloProvider client={client}>
      <h1>Hello</h1>
      <TodoList />
      <Todo />
      {/* コンポーネントを追加 */}
      <AddTodo />
    </ApolloProvider>
  );
}

export default App;
```

![add-todo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/56b999a3-ee0a-1a83-501e-b85415cbd909.gif)

- 最後におまけで、追加したTodoを画面に反映させたいのでTodoListを再取得するボタンを追加します

```jsx:src/components/TodoList.js
import React from 'react';
import { useQuery } from '@apollo/client';
import { GET_TODOS } from '../graphql/query';

function TodoList() {
  // refetchを追加
  const { loading, error, data, refetch } = useQuery(GET_TODOS);

  if (loading) return <p>...loading</p>;
  if (error) return <p>{error.message}</p>;

  return (
    <div>
      <h2>TodoList</h2>
      {/* クリックするとデータを再取得する */}
      <button onClick={() => refetch()}>REFETCH</button>
      {data.todos.map(todo => (
        <p key={todo.id}>{todo.id}: {todo.type}</p>
      ))}
    </div>
  );
}

export default TodoList;
```

![refetch.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/4f9331b0-7762-596a-ac87-797ea6739f2f.gif)

## まとめ

- Aplloを使うとloadingの管理などもやってくれるため本当に便利ですね
- とりあえず`useQuery`, `useLazyQuery`, `useMutation`の3つが使えればだいたいのことはできると思います
- これらを使った実装がとても簡単だと実感してもらえていれば幸いです