【Next.js】Firebase Storageに画像をアップしてURLを取得する
経緯
Firebase Storageを初めて使ってみて,とても使いやすいと感じたので共有したいと思いました.
Next.jsをフレームワークとして利用しています.
環境
react@17.0.2
firebase@8.6.8
typescript@4.3.4
next@11.0.1
tailwindcss@2.2.4
事前準備
事前にFirebaseプロジェクトを作成し,Reactのfirebase.jsなどにfirebaseの設定を記述してください.また
npm install firebase
or
yarn add firebase
によりfirebaseをインストールしてください.
import firebase from 'firebase/app';
import 'firebase/storage';
const firebaseConfig = {
// config
apiKey: 'API_KEY',
authDomain: 'AUTO_DOMAIN',
projectId: 'PROJECT_ID',
storageBucket: 'STORAGE_BUCKET',
messagingSenderId: 'MESSAGING_SENDER_ID',
appId: 'APP_ID',
measurementId: 'MEASUREMENT_ID'
};
// Initialize Firebase
if (firebase.apps.length === 0) {
firebase.initializeApp(firebaseConfig);
}
export const storage = firebase.storage();
export default firebase;
セキュリティルール
firebaseのセキュリティルールは動くかどうかを試すときはread, writeどちらともtrueに設定してください.試しが終わったら適宜セキュリティルールを設定してください.
Uploadコンポーネント
今回は画像をアップロードするコンポーネントを作成するので使いどころは適宜プロジェクトにより決めてください.関数や機能を他のファイルなどに切り分けても良いと思います.むしろその方がコードの可読性が上がりますが,今回は画像をアップロードするコンポーネントということにしたかったので全て1ファイルに記述しました.
import React, { useState, useCallback, useContext } from 'react';
import { useDropzone } from 'react-dropzone';
import firebase, { storage } from '../firebase/firebase';
export type firebaseOnLoadProp = {
bytesTransferred: number;
totalBytes: number;
state: firebase.storage.TaskState;
// このほかにもmetadata,task,refがある
};
const Upload: React.FC = () => {
const [myFiles, setMyFiles] = useState<File[]>([]);
const [clickable, setClickable] = useState(false);
const [src, setSrc] = useState('');
const onDrop = useCallback(async (acceptedFiles: File[]) => {
if (!acceptedFiles[0]) return;
try {
setMyFiles([...acceptedFiles]);
setClickable(true);
handlePreview(acceptedFiles);
} catch (error) {
alert(error);
}
}, []);
const onDropRejected = () => {
alert('画像のみ受け付けることができます。');
};
const { getRootProps, getInputProps } = useDropzone({
onDrop,
onDropRejected
});
const handleUpload = (accepterdImg: any) => {
try {
// アップロード処理
const uploadTask: any = storage
.ref(`/images/${myFiles[0].name}`)
.put(myFiles[0]);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
function (snapshot: firebaseOnLoadProp) {
const progress: number =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
console.log('Upload is paused');
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
console.log('Upload is running');
break;
}
},
function (error: any) {
// 失敗した時
switch (error.code) {
case 'storage/unauthorized':
// User doesn't have permission to access the object
console.error('許可がありません');
break;
case 'storage/canceled':
console.error('アップロードがキャンセルされました ');
// User canceled the upload
break;
case 'storage/unknown':
console.error('予期せぬエラーが発生しました');
// Unknown error occurred, inspect error.serverResponse
break;
}
},
function () {
// 成功した時
try {
uploadTask.snapshot.ref
.getDownloadURL()
.then(function (downloadURL: string) {
console.log('ダウンロードしたURL' + downloadURL);
});
} catch (error) {
switch (error.code) {
case 'storage/object-not-found':
console.log('ファイルが存在しませんでした');
break;
case 'storage/unauthorized':
console.log('許可がありません');
break;
case 'storage/canceled':
console.log('キャンセルされました');
break;
case 'storage/unknown':
console.log('予期せぬエラーが生じました');
break;
}
}
}
);
} catch (error) {
console.log('エラーキャッチ', error);
}
};
const handlePreview = (files: any) => {
if (files === null) {
return;
}
const file = files[0];
if (file === null) {
return;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
setSrc(reader.result as string);
};
};
return (
<div>
<div className="w-4/5 px-4 py-2 mx-auto my-4 text-center rounded-md">
<div
className="bg-gray-400 border-2 border-gray-500 rounded-md"
{...getRootProps()}
>
{/* この中をタップすれば画像を選択できる */}
<input {...getInputProps()} />
{myFiles.length === 0 ? (
<p className="py-4">画像を選択またはドラッグ&ドロップできます</p>
) : (
<div>
{myFiles.map((file: File) => (
<React.Fragment key={file.name}>
{src && <img src={src} />}
</React.Fragment>
))}
</div>
)}
</div>
<button
disabled={!clickable}
type="submit"
className="px-4 py-2 my-4 bg-gray-200 rounded-md"
onClick={() => handleUpload(myFiles)}
>
UPLOAD
</button>
</div>
</div>
);
};
export default Upload;
Upload.tsxの説明
行数が多いので分けて説明して行こうと思います.
export type firebaseOnLoadProp = {
bytesTransferred: number;
totalBytes: number;
state: firebase.storage.TaskState;
// このほかにもmetadata,task,refがある
};
Firebase Storageのアップロード状況を知る時に使用するtypeです.
詳しくはここに書かれています.
const onDrop = useCallback(async (acceptedFiles: File[]) => {
if (!acceptedFiles[0]) return;
try {
setMyFiles([...acceptedFiles]);
setClickable(true);
handlePreview(acceptedFiles);
} catch (error) {
alert(error);
}
}, []);
const onDropRejected = () => {
alert('画像のみ受け付けることができます。');
};
const { getRootProps, getInputProps } = useDropzone({
onDrop,
onDropRejected
});
画像をドロップした時に処理をする関数です.
onDropRejectedはドロップが失敗した時に実行されるので,そこはいろいろカスタマイズできると思います.
const handleUpload = (accepterdImg: any) => {
try {
// アップロード処理
const uploadTask: any = storage
.ref(`/images/${myFiles[0].name}`)
.put(myFiles[0]);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
function (snapshot: firebaseOnLoadProp) {
const progress: number =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
console.log('Upload is paused');
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
console.log('Upload is running');
break;
}
},
function (error: any) {
// 失敗した時
switch (error.code) {
case 'storage/unauthorized':
// User doesn't have permission to access the object
console.error('許可がありません');
break;
case 'storage/canceled':
console.error('アップロードがキャンセルされました ');
// User canceled the upload
break;
case 'storage/unknown':
console.error('予期せぬエラーが発生しました');
// Unknown error occurred, inspect error.serverResponse
break;
}
},
function () {
// 成功した時
try {
uploadTask.snapshot.ref
.getDownloadURL()
.then(function (downloadURL: string) {
console.log('ダウンロードしたURL' + downloadURL);
});
} catch (error) {
switch (error.code) {
case 'storage/object-not-found':
console.log('ファイルが存在しませんでした');
break;
case 'storage/unauthorized':
console.log('許可がありません');
break;
case 'storage/canceled':
console.log('キャンセルされました');
break;
case 'storage/unknown':
console.log('予期せぬエラーが生じました');
break;
}
}
}
);
} catch (error) {
console.log('エラーキャッチ', error);
}
};
画像のアップロードを実行する関数です.
今回は/imagesというディレクトリ以下に画像を保存していくようにしています.
uploadTask.on
はここでは4つの引数をとっています.
- 発火させる条件
- 発火した時に実行する関数
- アップロードが失敗した時に実行する関数
- 成功した時に実行する関数
それぞれ解説していきます.
発火させる条件
ここではタスクイベントの状態が変化した時としています.
発火した時に実行する関数
ここでは現在何%アップロードされているかを表示するようにしています.
bytesTransferredやtotalBytesを使って自分でローディングイベントを作成しても良いと思います.
アップロードに失敗した時に実行する関数
ここではエラーコードを読み取りそれに合わせてメッセージを表示しています.
エラーの種類は記載している他にもここに書いてあります.
アップロードに成功した時に実行する関数
ここでは成功するとその画像が挙げられているURLを取得するようにしています.またダウンロード時にエラーが起きた場合の処理も書いています.
エラーの種類は他にもあります.
const handlePreview = (files: any) => {
if (files === null) {
return;
}
const file = files[0];
if (file === null) {
return;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
setSrc(reader.result as string);
};
};
return (
<div>
<div className="w-4/5 px-4 py-2 mx-auto my-4 text-center rounded-md">
<div
className="bg-gray-400 border-2 border-gray-500 rounded-md"
{...getRootProps()}
>
{/* この中をタップすれば画像を選択できる */}
<input {...getInputProps()} />
{myFiles.length === 0 ? (
<p className="py-4">画像を選択またはドラッグ&ドロップできます</p>
) : (
<div>
{myFiles.map((file: File) => (
<React.Fragment key={file.name}>
{src && <img src={src} />}
</React.Fragment>
))}
</div>
)}
</div>
<button
disabled={!clickable}
type="submit"
className="px-4 py-2 my-4 bg-gray-200 rounded-md"
onClick={() => handleUpload(myFiles)}
>
UPLOAD
</button>
</div>
</div>
);
プレビューする時に行う処理と画面にレンダーする要素を記述しています.
React.Fragment
の中に画像を表示しています.
参考
Discussion