Skip to content

Commit

Permalink
Add useUploadFileResumable hook (#51)
Browse files Browse the repository at this point in the history
* init useUploadFileResumable hook

* test and impl initially, useUploadFileResumable hook > should have ready state

* impl useUploadFileResumable

* init docs for useUploadFileResumable

* explain useUploadFile performance and ui

* write docs for useUploadFileResumable
  • Loading branch information
erayerdin authored Feb 1, 2024
1 parent 1b95e42 commit dbb7d2d
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/hooks/useUploadFile.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tags:

# `useUploadFile` Hook

`useUploadFile` hook is used to upload a file to Firebase Storage. A very simple example would be:
`useUploadFile` hook is used to upload a file to Firebase Storage. It does not listen to the progress, thus, it is good for spinner-like indicators and quite performant. A very simple example would be:

```typescript
const reference = ref(storage, "path/to/remote/file.png");
Expand Down
52 changes: 52 additions & 0 deletions docs/hooks/useUploadFileResumable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
tags:
- hook
---

# `useUploadFileResumable` Hook

`useUploadFileResumable` hook is used to upload a file to Firebase Storage. It listens to the progress, thus, it is good for progressbar-like indicators but not performant at all since it causes frequent rerendering due to constant state changes. A very simple example would be:

```typescript
const reference = ref(storage, "path/to/remote/file.png");
const { dispatch } = useUploadFileResumable({ reference });
const result = await dispatch(file);
```

!!! warning
`useUploadFileResumable` is lazy by default and will not do anything until you use `dispatch` function.

You can listen to its state shown in example below:

```typescript
const { state } = useUploadFileResumable({ reference });
await dispatch();
// `state` is "ready" | [number, number] | "done"

if (typeof state === "array") {
const bytesTransferred = state[0];
const bytesTotal = state[1];
}
```

`dispatch` method will return an instance of [`UploadResult`][UploadResultRefDoc].

## Input Parameters

Input parameters for `useUploadFileResumable` hook is as follows:

| Name | Type | Description | Required | Default Value |
|---|---|---|---|---|
| `reference` | [`firebase/storage/StorageReference`][StorageReferenceRefDoc] | Reference to a file in Storage. || - |

## Return Type

`useUploadFileResumable` hook returns an object with properties as below:

| Name | Type | Description |
|---|---|---|
| `state` | `"ready" | [number, number] | "done"` | The state of the upload process. |
| `dispatch` | `(file: File | Blob | Buffer, metadata?: UploadMetadata) => Promise<UploadResult>` | A callback to start upload process. |

[StorageReferenceRefDoc]: https://firebase.google.com/docs/reference/js/storage.storagereference
[UploadResultRefDoc]: https://firebase.google.com/docs/reference/js/storage.uploadresult
3 changes: 2 additions & 1 deletion docs/storage/uploading-a-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

To upload file to Firebase Storage, you can use one of the following methods:

- [`useUploadFile` hook](../hooks/useUploadFile.md)
- [`useUploadFile` hook](../hooks/useUploadFile.md) to simply upload a file when you do not care about the progress (good for rendering a spinner, no constant state changes, thus performant)
- [`useUploadFileResumable` hook](../hooks/useUploadFileResumable.md) to upload a file while listening to the progress (good for rendering a progressbar, continuous state changes, thus a bit less performant)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ nav:
- hooks/useCallFunction.md
- Storage:
- hooks/useUploadFile.md
- hooks/useUploadFileResumable.md
- Components:
- Firestore:
- components/FirestoreDocument.md
Expand Down
2 changes: 2 additions & 0 deletions src/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
// https://opensource.org/licenses/MIT

export * from "./useUploadFile";

export * from "./useUploadFileResumable";
19 changes: 19 additions & 0 deletions src/storage/useUploadFileResumable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2024 Eray Erdin
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

import { renderHook } from "@testing-library/react";
import { ref } from "firebase/storage";
import { useUploadFileResumable } from ".";
import { storage } from "../firebase";

const reference = ref(storage, "files/README.md");

describe("initially, useUploadFileResumable hook", () => {
it("should have ready state", async () => {
const { result } = renderHook(() => useUploadFileResumable({ reference }));
const { state } = result.current;
expect(state).toBe("ready");
});
});
44 changes: 44 additions & 0 deletions src/storage/useUploadFileResumable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2024 Eray Erdin
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

import {
StorageReference,
UploadMetadata,
UploadResult,
uploadBytesResumable,
} from "firebase/storage";
import { useState } from "react";

type UseUploadFileResumableParams = {
reference: StorageReference;
};

type UseUploadFileResumableState = "ready" | [number, number] | "done";
type UseUploadFileResumableDispatcher = (
file: Buffer | File | Blob,
metadata?: UploadMetadata,
) => Promise<UploadResult>;
type UseUploadFileResumable = {
state: UseUploadFileResumableState;
dispatch: UseUploadFileResumableDispatcher;
};

export const useUploadFileResumable = ({
reference,
}: UseUploadFileResumableParams): UseUploadFileResumable => {
const [state, setState] = useState<UseUploadFileResumableState>("ready");

const dispatch: UseUploadFileResumableDispatcher = async (file, metadata) => {
const task = uploadBytesResumable(reference, file, metadata);
task.on("state_changed", (snapshot) => {
setState([snapshot.bytesTransferred, snapshot.totalBytes]);
});
const result = await task;
setState("done");
return result;
};

return { state, dispatch };
};

0 comments on commit dbb7d2d

Please sign in to comment.