AmplifyS3ImagePickerのコールバックが受け取りたい！

開発に便利なツールが揃っている Amplify

API だけではなく UI コンポーネントまで提供しておりコンポーネントを記述するだけで、簡単に認証やチャットボット等のリッチな UI を作ることができる

そんな便利な Amplify UI Component を使い倒そうと、Storage コンポーネント（画像とかテキストファイルのアップロード、ダウンロード）を試していたところ、アップロードの成功イベントが取得できなくて心が折れかけたので記事にします・・・

## 先に結論だけ

画像アップロード（だけではなく）のイベントハンドラは用意されていないので、[Hub](https://docs.amplify.aws/lib/utilities/hub/q/platform/js)という全体的なイベントを監視するライブラリを利用するらしい

いや、気づかんわ・・・・

## どんなコンポーネントがあるの？

[公式 Docs](https://docs.amplify.aws/ui/storage/s3-album/q/framework/react)に記載されているように、Amplify は Storage 利用のための UI コンポーネントがいくつか用意されています

### 画像系

- AmplifyS3Album
  - 画像をいっぺんに表示するやつ
- AmplifyS3Image
  - 画像を表示するやつ（アップロードはできない）
- AmplifyS3ImagePicker
  - 画像をアップロードできるやつ（プレビュー表示がついている）

### テキスト系

こっちは試していないので名前だけで判断

- AmplifyS3Text
  - 画像以外のファイルを表示・ダウンロードできるやつ？
- AmplifyS3TextPicker
  - 画像以外のファイルをアップロード・プレビュー表示するやつ？

## 何が問題だったのか

### 実装したかった流れ

1. 画像をアップロードする
1. 帰ってきたキー名を AppSync にて imageKey として保存
1. 次画面を表示した際に AppSync より imageKey を取得
1. imageKey を元に S3Image に画像を表示

### コンポーネントの簡単な説明

AmplifyS3Album や AmplifyS3ImagePicker で画像をアップロードすることができる

```tsx
import { AmplifyS3ImagePicker } from "@aws-amplify/ui-react"

function App() {
  return (
    <>
      <AmplifyS3ImagePicker />
    </>
  )
}
```

![スクリーンショット 2021-05-28 11.03.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/9910c428-2b93-0d73-b94d-ac7299c66890.png)


ファイルの選択フォーム、プレビュー機能、アップロードボタンがついて、アップロードの処理も最初から準備されている
もうこれそのまま使うだけでいいじゃん www と思いとりあえず使ってみた

### いざ実証！

とりあえずアップロードをしてみた

・・・・・・・・

何も起こらない
デベロッパーツールでコンソールを見ても特に何も起きてない

Network タブを見るとリクエストが飛んでいることがわかった

あれ、一応保存だけはできてるみたいだ・・・

じゃあイメージのキーはどうやって取得するの？

公式 Docs を舐めまわしてもアップロードのコールバックとかは無さそう

**え、結果どうやって受け取るの・・・？？？？**

## 色々と調査した結果

AmplifyS3ImagePicker について日本語の記事（というより英語の記事も）があまりなく、検索しても中々出てきませんでしたが、ようやく[Issue](https://github.com/aws-amplify/amplify-js/issues/6866)でそれっぽいものを見つけました

> I had some help from the aws subreddit, so, basically you need to use a Hub.Listen("storage") for the upload success event. That initially wasn't working until I added the "track" param to the image picker as well. Then i finally received the storage events that the image picker was using. This should definitely be mentioned in the documentation, because the storage events do not fire without the track param added!
>
> Unfortunately, there is not enough customization available with the current image picker, no cropper, no dimension restrictions, no resize ability and no file type restrictions, so I ended up writing a custom image picker using a file input, an image cropper library and the Storage.put method

要するに Hub ライブラリを使って、イベントログをトレースして取得するしかないということでした

Hub は Amplify が提供している、諸々のイベントを監視してトレースログみたいな形で吐き出してくれる奴みたいです（10 分くらいしか調べてないので理解度がｵﾜｯﾃﾙ）
イベントを実行することもできるみたいです(dispatch)

storage 以外にも

- auth
- api
- analytics
- datastore
  等、様々な機能のイベントを扱うことができます

## 実際に Hub を利用してみた

```tsx
function App() {
  // 画面表示時から監視をスタート
  useEffect(() => {
    Hub.listen("storage", (data) => console.log(data))
  }, [])

  return (
    <>
      <AmplifyS3ImagePicker trace /> // traceを有効にしないとHub側で取得することができません！
    </>
  )
}
```

実際にアップロードしてみると・・・ログを取得できました！



アップロード成功時、その後の画像取得処理のログが表示されています

![スクリーンショット 2021-05-28 12.19.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461616/9d9877a2-44bd-601d-2cad-2c16785b12f2.png)
一部ぼかしを入れています

この情報があれば一応アップロード成功・失敗や画像のキーを取得することができそうです

payload.messageには`Upload success for キー名`という文字列を受け取れているので、
`payload.message.replace("Upload success for ", "")`でキー名を取得します（このやり方が正しいのかはわからない・・・）

```tsx
  useEffect(() => {
    Hub.listen("storage", ({ payload }) => {
      if (payload.event === "upload" && payload.message) {
        const imageKey = payload.message.replace("Upload success for ", "")
        if (payload.data.attrs.result === "success") {
          console.log(`アップロードに成功しました。`)
        } else {
          Storage.remove(imageKey)
          console.log(`アップロードに失敗しました。`)
        }
      }
    })
    return () => {
      Hub.remove("storage", () => {})
    }
  }, [])
```

## まとめ

Amplify UI Component は非常に便利ですが、やはり OSS なので一部未実装な機能があるみたいです

Hub は便利ですが、不要な情報も取得できてしまうのとか、ずっとイベント監視していてパフォーマンスは平気なのかとか、懸念点はいっぱいあるのでこれからも調査をしたいと思います

Storage に限っては API のドキュメントがとても読みやすいので、自分で UI を実装してしまう方が柔軟性はあると思いました
でもデザインできないから UI もらえるなら使っちゃいますね

とりあえずコンポーネントに直接イベントハンドラを渡せるようにして欲しいです・・・