Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A react hook that handles firebase storage uploading
import type {
StorageReference,
UploadMetadata,
UploadTask,
} from 'firebase/storage';
import { useEffect, useReducer } from 'react';
import { ref, uploadBytesResumable, getDownloadURL, getStorage } from 'firebase/storage';
export enum UploadState {
IDLE = 'IDLE',
PAUSED = 'PAUSED',
UPLOADING = 'UPLOADING',
CANCELED = 'CANCELED',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
}
enum ActionType {
SET_UPLOAD_ERROR,
SET_UPLOAD_PROGRESS,
SET_UPLOAD_COMPLETED,
START_UPLOAD,
PAUSE_UPLOAD,
RESUME_UPLOAD,
CANCEL_UPLOAD,
INITIALIZE,
}
type UploaderState = {
progress?: number;
state: UploadState;
fileUrl?: string;
error?: Error;
_file?: {
file: File;
metadata?: UploadMetadata;
};
_uploadTask?: UploadTask;
storageRef?: StorageReference;
};
type UploaderAction =
| {
type: ActionType.SET_UPLOAD_PROGRESS;
payload: { progress: number };
}
| {
type: ActionType.SET_UPLOAD_ERROR;
payload: { error: Error };
}
| {
type: ActionType.SET_UPLOAD_COMPLETED;
payload: { fileUrl: string };
}
| {
type: ActionType.INITIALIZE;
payload: { file: File; metadata?: UploadMetadata };
}
| {
type: ActionType.START_UPLOAD;
payload: { uploadTask: UploadTask; storageRef: StorageReference };
}
| {
type: ActionType.PAUSE_UPLOAD;
}
| {
type: ActionType.RESUME_UPLOAD;
}
| {
type: ActionType.CANCEL_UPLOAD;
};
function reducer(state: UploaderState, action: UploaderAction): UploaderState {
switch (action.type) {
case ActionType.INITIALIZE:
return {
...state,
_file: {
file: action.payload.file,
metadata: action.payload.metadata,
},
};
case ActionType.START_UPLOAD:
return {
...state,
progress: 0,
state: UploadState.UPLOADING,
_uploadTask: action.payload.uploadTask,
storageRef: action.payload.storageRef,
};
case ActionType.PAUSE_UPLOAD:
return {
...state,
state: UploadState.PAUSED,
};
case ActionType.RESUME_UPLOAD:
return {
...state,
state: UploadState.UPLOADING,
};
case ActionType.CANCEL_UPLOAD:
return {
...state,
state: UploadState.CANCELED,
};
case ActionType.SET_UPLOAD_PROGRESS:
return {
...state,
progress: action.payload.progress,
};
case ActionType.SET_UPLOAD_ERROR:
return {
...state,
state: UploadState.FAILED,
error: action.payload.error,
};
case ActionType.SET_UPLOAD_COMPLETED:
return {
...state,
progress: 100,
state: UploadState.COMPLETED,
fileUrl: action.payload.fileUrl,
};
default:
throw new Error('Unhandled action type');
}
}
export function useFirebaseUploader(path?: string) {
const [
{ progress, state, error, fileUrl, storageRef, _file, _uploadTask },
dispatch,
] = useReducer(reducer, {
state: UploadState.IDLE,
});
useEffect(() => {
if (_file && path) {
const storage = getStorage();
const storageRef = ref(storage, path);
const uploadTask = uploadBytesResumable(
storageRef,
_file.file,
_file.metadata
);
dispatch({
type: ActionType.START_UPLOAD,
payload: { uploadTask, storageRef },
});
const unsubscribe = uploadTask.on(
'state_changed',
(snapshot) => {
const progress = Math.floor(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
if (snapshot.state === 'running') {
dispatch({
type: ActionType.SET_UPLOAD_PROGRESS,
payload: { progress },
});
}
},
(error) => {
switch (error.code) {
case 'storage/unauthorized':
dispatch({
type: ActionType.SET_UPLOAD_ERROR,
payload: {
error: new Error(
'User does not have permission to access the storage object'
),
},
});
break;
case 'storage/canceled':
break;
case 'storage/unknown':
default:
dispatch({
type: ActionType.SET_UPLOAD_ERROR,
payload: {
error: new Error('Upload failed due to an unknown error'),
},
});
break;
}
unsubscribe();
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
completeDownload(downloadURL);
});
unsubscribe();
}
);
return () => {
unsubscribe();
};
}
}, [_file, path]);
const start = (file: File, metadata?: UploadMetadata) => {
dispatch({
type: ActionType.INITIALIZE,
payload: { file, metadata },
});
};
const pause = () => {
if (_uploadTask) {
_uploadTask.pause();
dispatch({ type: ActionType.PAUSE_UPLOAD });
}
};
const resume = () => {
if (_uploadTask) {
_uploadTask.resume();
dispatch({ type: ActionType.RESUME_UPLOAD });
}
};
const cancel = () => {
if (_uploadTask) {
_uploadTask.cancel();
dispatch({ type: ActionType.CANCEL_UPLOAD });
}
};
const completeDownload = (fileUrl: string) => {
dispatch({
type: ActionType.SET_UPLOAD_COMPLETED,
payload: { fileUrl },
});
};
return {
progress,
state,
fileUrl,
error,
start,
pause,
resume,
cancel,
storageRef,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment