Twitter clone Part10 firebaseに投稿情報を加える

# 概要
今回はfirebaseに投稿情報を加える機能を実装します。
以下投稿時のウェブアプリとfirebaseの動きです。

![PostData12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/47b93c02-ad89-a3f2-923d-6794b86ae906.gif)



# 開発環境

OS:Windows10
IDE:VSCode


```package.json
{
  "name": "",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@heroicons/react": "^1.0.6",
    "next": "12.0.9",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/node": "^20.4.2",
    "@types/react": "^18.2.15",
    "autoprefixer": "^10.4.2",
    "eslint": "8.8.0",
    "eslint-config-next": "12.0.9",
    "postcss": "^8.4.5",
    "tailwindcss": "^3.0.18"
  }
}
```

# 実装のポイント

## firebaseのFireStore DatabaseとStorageを設定する

### firestore Databaseを始める
以下の手順でfirestorebaseの設定を行います。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/06affcaf-c8a7-7aab-a86e-a3f399e35d34.png)


操作後　パネルビューが表示されていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/7738ee7c-1e7f-a9dc-5a2c-ebad986d4073.png)


### Storageを始める
以下の手順でStorageを始めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/385e8a06-5684-889e-6128-6d057c7466e0.png)


操作後、Storageの作成が完了していることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/5dc09f8f-2979-c5eb-85cb-0cf26739e879.png)



## 画面を実装する



## 画像アップロード機能を加える input type="file"のUIを隠すため　useRefを使う
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/a3631cb2-77e2-7094-c3a1-6f212f27cf3d.png)


## 画像アップロード後、Xボタンを押して画像を削除することができる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/a968a6dc-8cd8-b2bd-80ee-ed21623f7e2d.png)

## firebaseに投稿情報を加える

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/8a72146d-1fc4-058f-3df3-eb0d022d5132.png)




# コード部分
## Input
```diff_jsx:Input.jsx

+import {EmojiHappyIcon, PhotographIcon, XIcon} from '@heroicons/react/outline';
import {useSession, signOut} from "next-auth/react";
+import{addDoc,collection,doc,serverTimestamp,updateDoc} from "firebase/firestore"
+import{getDownloadURL, ref, uploadString} from "firebase/storage";
+import{db, storage} from "../firebase";
+import {useState,useRef} from "react";

export default function Input(){
    const {data: session} = useSession();
+    const [input,setInput] = useState("");
+    const [selectedFile, setSelectedFile] = useState(null);
+    const [loading,setLoading] = useState(false);
+    const filePickerRef = useRef(null);

+    const sendPost = async () =>{
+        if(loading) return;
+        setLoading(true);

+        const docRef = await addDoc(collection(db,"posts"),{
+            id: session.user.uid,
+            text:input,
+            userImg:session.user.image,
+            timestamp: serverTimestamp(),
+            name: session.user.name,
+            username: session.user.username
+        });
    
+        const imageRef = ref(storage, `posts/${docRef.id}/image`);
+        if(selectedFile){
+            await uploadString(imageRef, selectedFile,"data_url").then(async() =>{
+                const downloadURL = await getDownloadURL(imageRef);
+                await updateDoc(doc(db,"posts",docRef.id),{
+                    image: downloadURL,
+                });
+            });
+        }
+    
+        setInput("");
+        setSelectedFile(null);
+        setLoading(false);
+    };

+    const addImageToPost = (e) =>{
+        const reader = new FileReader();
+        if(e.target.files[0]){
+            reader.readAsDataURL(e.target.files[0]);
+        }
+
+        reader.onload = (readerEvent) =>{
+            setSelectedFile(readerEvent.target.result);
+        }
+    }


    return(
        <>
            {session && (
                <div className="flex border-b border-gray-200 p-3 space-x-3">
                
                    <img
                        onClick={signOut}
                        src={session.user.image}
                        alt="user-img"
                        className="h-11 w-11 rounded-full cursor-pointer hover:brightness-95"
                    />
                    <div className="w-full dived-y devide-gray-200">
                        <div className="">
+                            <textarea 
+                                className="w-full border-none focus:ring-0 text-lg placeholder-gray-700"
+                                rows="2"
+                                placeholder="What`s happening?"
+                                value={input}
+                                onChange={(e) => setInput(e.target.value)}
+                            ></textarea>
                        </div>
+                        {selectedFile &&(
+                            <div className="relative">
+                                <XIcon
+                                    onClick={() => setSelectedFile(null)}
+                                    className="h-7"
+                                    />
+                                <img
+                                    src={selectedFile}
+                                    className={`${loading && "animate-pulse"}`}
+                                />
                            </div>
+                        )}
+                        <div className="flex items-center justify-between pt-2.5">   
+                        {!loading &&(
+                                <>
+                                    <div className="flex" >
+                                        <div className="" onClick={() => filePickerRef.current.click()}>
+                                            <PhotographIcon className="h-10 w-10 hoverEffect p-2 text-sky-500 hover:bg-sky-100" />
+                                            <input type="file" hidden ref={filePickerRef} onChange={addImageToPost} />
+                                        </div>
+                                        <EmojiHappyIcon className="h-10 w-10 hoverEffect p-2 text-sky-500 hover:bg-sky-100" />
+                                    </div>                                    <button onClick={sendPost} disabled={!input.trim()} className="bg-blue-400 text-white px-4 py-1.5 rounded-full font-bold">Tweet</button>                       
                                </>
                                )} 
                        </div>
                    </div>
                </div>
            )}
        </>
    )
}
```





# 参考

## css

## javascript

### FileReader オブジェクト

https://developer.mozilla.org/ja/docs/Web/API/FileReader

### FileReader.readAsDataURL()

https://developer.mozilla.org/ja/docs/Web/API/FileReader/readAsDataURL

### event.target.files

https://www.pazru.net/html5/File/050.html

## Next.js


## React

### useRef

https://react.dev/reference/react/useRef

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/c98c9711-be24-e45a-1241-ff668f456a71.png)


## firebase

### addDoc

https://github.com/firebase/snippets-web/blob/88aa8eb8210c90263eef7fe1f454cd6477a5a252/snippets/firestore-next/test-firestore/add_document.js#L8-L15

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/86c8c7f2-dc55-74d6-2807-69ae0d2ca1e9.png)


### updateDoc  画像ファイルがある場合、投稿情報に画像のパスを加える

https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/39ee1070-8518-8c48-faaf-13d98f13b7eb.png)


### uploadString 文字列から画像をアップロードする

https://firebase.google.com/docs/storage/web/upload-files?hl=ja#web-modular-api_1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/34995101-a262-490b-99b5-f56d29f6b750.png)


## その他

Udemy

78. Send data to firebase and add loading effect

https://www.udemy.com/course/react-js-tutorial/learn/lecture/32461766?start=660#overview

githubコミット分

https://github.com/RYA234/twitter-clone/commit/f0e512863885c240de7be75d26930ea9ef6baa60