Amplify CLIで作ったGraphQL APIクエリするテンプレ + Authenticator コンポーネントのまとめ

## 何をするか
Amplify CLIで以下を作成する。

- Cognitoで認証されたユーザーだけがクエリを実行できるGraphQL API
- React.jsのコード
    - Authenticatorコンポーネントによる認証
    - Cognitoで認証されてAPIへクエリする　(認証とUIのテンプレなのでクエリ内容は固定、しかも結果もコンソールに表示するのみ)
    - 認証状態が変わるとボタンの表示非表示が変わる
    - material-ui入れる (今回のコードではボタンしか使っていないため、@aws-amplify/ui-reactだけでもいい)

Amplifyのドキュメントは必要情報自体はあるけれど、情報が散在しているためシンプルな構成をガガッと試したい時に結構時間かかる。今回一番苦労したのはAuthenticatorコンポーネントが何者なのかというところ(複数のバージョンがあり紛らわしい)。

ログイン前の画面
![スクリーンショット 2022-01-23 22.30.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490290/f92c6040-55c0-efdd-2072-32f9fad81781.png "ログイン前")


ログイン後の画面
![スクリーンショット 2022-01-23 22.30.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490290/2e63d728-1ad7-f617-301b-cc997276fa09.png "ログイン後")


CALLボタンは同じコンポーネントの表示位置を変えているだけだが、ログインするとAPIリクエストにユーザー情報が勝手に付与されるようになる。

## 環境とパッケージ
今回はこれがメモりたいために書いたようなもの。新たに作成しようとするとバージョンの問題なのかエラーが発生してしんどいので、動くバージョン群をメモしておく。

Amazon Linux 2
node -v -> v16.4.0
npm -v -> 7.18.1
amplify -v -> 7.6.5

```json:package.json　(のdependencies)
  "dependencies": {
    "@aws-amplify/ui-react": "^2.1.0",
    "@emotion/react": "^11.7.1",
    "@emotion/styled": "^11.6.0",
    "@material-ui/core": "^5.0.0-beta.5",
    "aws-amplify": "^4.3.11",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.3"
  }
```

## 作成手順
こんな感じ。細かい手順は成り行きで。

```sh
npx create-react-app testapp
cd testapp
amplify init
amplify add api # Cognito選択
amplify push
touch src/APICall.js
```

## コード

```react:APICall.js
import React from "react";
import { Auth, API, graphqlOperation } from 'aws-amplify';
import { testQuery } from './graphql/queries'

import { Button } from '@material-ui/core'

const APICall = () => {
    const call = async () => {
        try{
            const userInfo = await Auth.currentAuthenticatedUser();
            console.log(userInfo);
        } catch (e) {
            console.log('not authorized dayo')
        }
        try{
            // 認証されていてもいなくてもリクエストは送る
            const res = await API.graphql(graphqlOperation(testQuery))
            console.log(res)
        } catch (e) {
            console.log(e);
        }
    }

    return(
        <>
        <Button variant="contained" onClick={call}>call button</Button>
        </>
    )
}

export default APICall;
```

```react:App.js
import React, { useEffect, useState } from 'react';

import { Amplify, Auth, Hub } from 'aws-amplify';
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import APICall from './APICall'

import awsExports from './aws-exports';
Amplify.configure(awsExports);

const App = () => {
  const [loginState, setLoginState] = useState(null);

  useEffect(() => {
    // ログイン状態を loginState にセット
    const authenticationStateCheck = async () => {
      try{
        const userInfo = await Auth.currentAuthenticatedUser();
        setLoginState(userInfo);
      }catch(e){
        setLoginState(null);
      }
    }
    // ページロード時にログイン状態をチェック
    authenticationStateCheck();

    // 認証状態変更時にログイン状態をチェック
    Hub.listen('auth', (data) => {
      authenticationStateCheck();
    })
  }, []);
 
  return (
    <>
    <Authenticator >
    {({ signOut, user }) => (
        <main>
          <h1>Hello {user.username}</h1>
          ログインしている時はここにボタンが現れる↓
          <br/>
          <APICall />
          <button onClick={signOut}>Sign out</button>
        </main>
      )}
    </Authenticator>
    ログインしてない時はここにボタンが現れる↓
    <br/>
    {loginState ? "no button" : <APICall />}
    </>
  );
}

export default App;
```

```graphql:schema.graphql
## どんなものを投げてもLambdaが何かしら結果を返してくれるというようなおサボりをしている
type Query {
  testQuery (id: ID): String
  @function(name: "<Lambda関数名>-${env}")
  @auth(rules: [{allow: private, provider: userPools},])
}
```


## 困っていたこと (Authenticatorコンポーネントの使い方)
`Authenticator`にはLatestとLegacyの情報が散在しており紛らわしい。
例えば、[このdocument](https://docs.amplify.aws/lib/auth/customui/q/platform/js/#customize-ui-theme)では`Authenticator`から`onStateChange`を`props`として子コンポーネントに渡せるというふうに書いてあったが、importしているのが`aws-amplify-react`なのでこれはLegacy.  `@aws-amplify/ui-react`　v2.1.0の`Authenticator`([document](https://ui.docs.amplify.aws/components/authenticator))では動かなかった。
後は`AmplifyAuthenticator`というコンポーネントの存在も紛らわしく、`AmplifyAuthenticator` => Legacy, `Authenticator` => Latestと対応するものと思ってしまっていた。しかし[レガシーとされているパッケージ](https://docs.amplify.aws/ui-legacy/q/framework/react/)を見ると、reactだけでも二つ記載されていた。正しくは多分以下。


| コンポーネント | パッケージ | 関連リンク |Legacy or Latest|
|:-:|:-:|:-:|:-:|
| AmplifyAuthenticator  | @aws-amplify/ui-react@v1  | [Github](https://github.com/aws-amplify/amplify-ui/tree/legacy/legacy/amplify-ui-react)  |Legacy|
|  Authenticator |  aws-amplify-react |  [Github](https://github.com/aws-amplify/amplify-ui/blob/legacy/legacy/aws-amplify-react/README.md) |Legacy|
| Authenticator  | @aws-amplify/ui-react@v2  |  [document](https://ui.docs.amplify.aws)や[Github](https://github.com/aws-amplify/amplify-js/tree/master/packages/amplify-ui-react) |Latest|

## 知ったこと
- AuthenticatorのLegacyとLatestの見分け方。
- 今はLatestで認証状態の変化を取得するにはHubを使うしかない？(そこまでしっかり調べてないです...)
- `API.graphql()`でリクエストするとログイン状態に応じて認証情報を付与してくれる。GraphQL APIがIAM認証の場合は勝手に認証ロールor未認証ロールでのリクエストになってくれる。