React+GCPで、画像のプレビュー表示と、画像保存を行う

# はじめに
SNSとかでよくある、
「プロフィール画像を選択」
→「画像のプレビューが出る」
→「外部ストレージに保存する」

というのを、React + Firestore(DB) + Firebasestorage(Storage)で行う方法です。

なにか他に良い方法などあれば、ご教授いただけると非常にありがたいです！

ちなみに、今回は画像をそのまま送っていますが、
[react-image-crop](https://github.com/DominicTobias/react-image-crop)このライブラリを使えば、
画像サイズの調整なども簡単にできます

## なにをすればいいのかざっくり説明

今回のテーマは、「プロフィール画像の変更」なので、
プロフィールには、
・プロフィール画像
・名前
の2つのフィールドがあることとします。


ここで行う処理を先に整理しておくと、

1. componentDidMountで、現在のプロフィール画像を取ってくる

2. input[type='file']で、画像ファイルを受け取る

3. 受け取ったファイルを参照できる仮のURLを作成して、renderしているphotoURLをそちらに変更.
   storageに送る用のfileを別で保持しておく。

4. 「保存ボタン」を押したら、保持しているfileをFirebaseStorageにおくり、それを参照できるURLを作る

5. Firestoreを更新

# やってみよう

### render -> componentDidMountまで

```ProfileSetting.jsx
export default class ProfileSetting extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      photoURL: 'https://{イメージがない時に表示する画像パス}',
      blob: {},
      imgChanged: false,
      saving: false
    }
  }
  handleChangeFile(e) {
    // ファイルが上がった時の処理
  }
　　　submit = async () => {
    // 保存する
  }

  componentDidMount　= async () => {
    // ユーザを取ってくる処理
    const user = Backend.getUser()
    this.setState({ ...user.data() })
  }
  render() {
    const { photoURL, blob, saving } = this.state
    return (
      <div style={styles.wrapper}>
        <h1  style={styles.heading}>プロフィール画像を設定する記事を書く</TitleText>
        <img style={styles.profileImg} src={photoURL} />
        <input type="file" disabled={saving} onChange={(e) => this.handleChangeFile(e)} />
      </Wrapper>
    )
  }
}

```
```Backend.js

getUser = _ => {
  // this.uid は、プロフィール編集者のuidとします
  return firestore.collection("users").doc(this.uid).get()
}

```

ここまでで、下みたいな感じになります(スタイルはこちらで適当に当ててますが)

ここで、画像の処理中(saving = trueのとき)に、
inputを押させないようにします。

<img width="893" alt="スクリーンショット 2018-10-18 21.00.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/183240/f4acfd96-ad15-2ee9-5e40-ee0654d242ef.png">

## ファイルがあがったときの処理（handleChangeFile）

```ProfileSetting.js
...
const createObjectURL = (window.URL || window.webkitURL).createObjectURL || window.createObjectURL;

handleChangeFile(e){
  const file = e.target.files[0];
  const image_url = createObjectURL(file);
  this.setState({
    photoURL: image_url,
    file: file
  })
}

...
```

input[type='file']にしたときは、ファイルはe.target.filesの配列になって入っています。
multiple属性をつけていると、複数メンバが入りますが、今回は一個のファイルしか扱わないので、
e.target.files[0]で良いです。

そして、createObjectURLで、ファイルの内容を参照可能な一時URLを作ります。

そのときの画像がこちら
<img width="968" alt="スクリーンショット 2018-10-18 21.17.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/183240/39c15c78-4c93-9356-6bc0-7742722fae8c.png">

プロフィール画像が、さっきのスクリーンショットになっていますね。
これで、とりあえずプレビューは完成です。

## inputからfirebaseに送る処理(submit)

```ProfileSetting.js
...

submit = async() => {
  this.setState({ saving: true })
  const { changedImg, file, displayName } = this.state
  const photoURL = await Backend.uploadImage({type: 'image', file: file})
　　　　await Backend.updatePhotoURL({
    displayName: displayName, 
    photoURL: photoURL
  })
  this.setState({ saving: false })
  alert("保存しました！")
}
```
## Firestoreと、FirestorageでストレージとDBの更新処理

```Backend.js

// ストレージにイメージをアップロードして、それを参照可能なURLをreturnする！
uploadImage = async ({type, file}) => {
  const storageRef = firebase.app().storage().ref(`${type}/${this.user.uid}`)
  await storageRef.put(file)
  return storageRef.getDownloadURL()
}

// ユーザのデータを更新する
updatePhotoURL = ({displayName, photoURL}) => {
  return usersRef.doc(this.uid).update({
    displayName: displayName,
    photoURL: photoURL
    updatedAt: firebase.firestore.FieldValue.serverTimestamp()
  })
}
```

このようにして、更新できます！