Skip to content

Commit

Permalink
Make useFileUpload generic (#4)
Browse files Browse the repository at this point in the history
* Make generic

* Update readme

* Update readme

* Ensure item has new startedAt on retry

* Bump package
  • Loading branch information
rossmartin authored Nov 11, 2022
1 parent 28b6f5f commit 9e46e4e
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 49 deletions.
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@ There is an example app in this repo as shown in the above gif. It is located wi
```tsx
import useFileUpload, { UploadItem } from 'react-native-use-file-upload';

// Used in optional type parameter for useFileUpload
interface Item extends UploadItem {
progress?: number;
}

// ...
const [data, setData] = useState<UploadItem[]>([]);
const { startUpload, abortUpload } = useFileUpload({
const [data, setData] = useState<Item[]>([]);
// The generic type param below for useFileUpload is optional
// and defaults to UploadItem. It should inherit UploadItem.
const { startUpload, abortUpload } = useFileUpload<Item>({
url: 'https://example.com/upload',
field: 'file',
// Below options are optional
method: 'POST',
headers,
timeout: 45000,
timeout: 60000,
onProgress,
onDone,
onError,
Expand All @@ -51,7 +58,7 @@ const onPressUpload = async () => {
Start a file upload for a given file. Returns a promise that resolves with `OnDoneData` or rejects with `OnErrorData`.

```ts
// Objects passed to startUpload should have the below shape (UploadItem type)
// Objects passed to startUpload should have the below shape at least (UploadItem type)
startUpload({
name: 'file.jpg',
type: 'image/jpg',
Expand Down Expand Up @@ -128,7 +135,7 @@ useFileUpload({ headers });
```ts
// OnProgressData type
{
item: UploadItem;
item: UploadItem; // or a type that inherits UploadItem
event: ProgressEvent<EventTarget>;
};
// event is the XMLHttpRequest progress event object and it's shape is -
Expand All @@ -149,7 +156,7 @@ useFileUpload({ headers });
```ts
// OnDoneData type
{
item: UploadItem;
item: UploadItem; // or a type that inherits UploadItem
responseBody: string; // eg "{\"foo\":\"baz\"}" (JSON) or "foo"
responseHeaders: string;
}
Expand All @@ -166,7 +173,7 @@ useFileUpload({ headers });
```ts
// onErrorData type
{
item: UploadItem;
item: UploadItem; // or a type that inherits UploadItem
error: string;
}
```
Expand All @@ -182,7 +189,7 @@ useFileUpload({ headers });
```ts
// OnErrorData type
{
item: UploadItem;
item: UploadItem; // or a type that inherits UploadItem
error: string;
timeout: boolean; // true here
}
Expand Down
35 changes: 13 additions & 22 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function App() {
const [data, setData] = useState<Item[]>([]);
const dragStartAnimatedValue = useRef(new Animated.Value(1));

const { startUpload, abortUpload } = useFileUpload({
const { startUpload, abortUpload } = useFileUpload<Item>({
url: 'http://localhost:8080/upload',
field: 'file',
// optional below
Expand Down Expand Up @@ -109,13 +109,7 @@ export default function App() {
});
};

async function onProgress({
item,
event,
}: {
item: Item;
event: OnProgressData['event'];
}) {
async function onProgress({ item, event }: OnProgressData<Item>) {
const progress = event?.loaded
? Math.round((event.loaded / event.total) * 100)
: 0;
Expand All @@ -125,16 +119,16 @@ export default function App() {
// This is needed after moving to FastImage?!?!
const now = new Date().getTime();
const elapsed = now - item.startedAt!;
if (progress >= 100 && elapsed <= 200) {
if (progress === 100 && elapsed <= 200) {
for (let i = 0; i <= 100; i += 25) {
updateItem({
item,
keysAndValues: [
{
key: 'progress',
value: i,
},
],
setData((prevState) => {
const newState = [...prevState];
const itemToUpdate = newState.find((s) => s.uri === item.uri);
if (itemToUpdate) {
// item can fail before this hack is done because of the sleep
itemToUpdate.progress = itemToUpdate.failed ? undefined : i;
}
return newState;
});
await sleep(800);
}
Expand Down Expand Up @@ -165,7 +159,7 @@ export default function App() {
// :~)
const putItOnTheLine = async (_data: Item[]) => {
const promises = _data
.filter((item) => typeof item.progress !== 'number') // leave out any in progress
.filter((item) => typeof item.progress !== 'number') // leave out any in progress or completed
.map((item) => startUpload(item));
// use Promise.all here if you want an error from a timeout or error
const result = await allSettled(promises);
Expand Down Expand Up @@ -215,10 +209,7 @@ export default function App() {
},
],
});
// wrapped in try/catch here just to get rid of possible unhandled promise warning
try {
await startUpload(item);
} catch (_ex) {}
startUpload({ ...item, startedAt: new Date().getTime() }).catch(() => {});
};

const onDragStart = () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-use-file-upload",
"version": "0.1.2",
"version": "0.1.3",
"description": "A hook for uploading files using multipart form data with React Native. Provides a simple way to track upload progress, abort an upload, and handle timeouts. Written in TypeScript and no dependencies required.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
14 changes: 7 additions & 7 deletions src/hooks/useFileUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
UploadItem,
} from '../types';

export default function useFileUpload({
export default function useFileUpload<T extends UploadItem = UploadItem>({
url,
field,
method = 'POST',
Expand All @@ -16,12 +16,12 @@ export default function useFileUpload({
onDone,
onError,
onTimeout,
}: FileUploadOptions) {
}: FileUploadOptions<T>) {
const requests = useRef<{
[key: string]: XMLHttpRequest;
}>({});

const startUpload = (item: UploadItem): Promise<OnDoneData | OnErrorData> => {
const startUpload = (item: T): Promise<OnDoneData<T> | OnErrorData<T>> => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append(field, item);
Expand All @@ -41,7 +41,7 @@ export default function useFileUpload({
if (timeout) {
xhr.timeout = timeout;
xhr.ontimeout = () => {
const result: OnErrorData = {
const result: OnErrorData<T> = {
item,
error: xhr.responseText,
timeout: true,
Expand All @@ -52,7 +52,7 @@ export default function useFileUpload({
}

xhr.onload = () => {
const result: OnDoneData = {
const result: OnDoneData<T> = {
item,
responseBody: xhr.response || xhr.responseText,
responseHeaders: xhr.getAllResponseHeaders(),
Expand All @@ -62,7 +62,7 @@ export default function useFileUpload({
};

xhr.onerror = () => {
const result: OnErrorData = {
const result: OnErrorData<T> = {
item,
error: xhr.responseText,
};
Expand All @@ -71,7 +71,7 @@ export default function useFileUpload({
};

xhr.onabort = () => {
const result: OnErrorData = {
const result: OnErrorData<T> = {
item,
error: 'Request aborted',
};
Expand Down
27 changes: 16 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,37 @@ export type UploadItem = {
uri: string;
};

export type OnProgressData = {
item: UploadItem;
// "T extends UploadItem = UploadItem"
// Generic type parameter that allows passing
// a custom type that inherits UploadItem (constraint).
// It defaults to UploadItem when no type argument is passed.

export type OnProgressData<T extends UploadItem = UploadItem> = {
item: T;
event: ProgressEvent<EventTarget>;
};

export type OnDoneData = {
item: UploadItem;
export type OnDoneData<T extends UploadItem = UploadItem> = {
item: T;
responseBody: string;
responseHeaders: string;
};

export type OnErrorData = {
item: UploadItem;
export type OnErrorData<T extends UploadItem = UploadItem> = {
item: T;
error: string;
timeout?: boolean;
};

export type FileUploadOptions = {
export type FileUploadOptions<T extends UploadItem = UploadItem> = {
url: string;
field: string;
// optional below
method?: string;
headers?: Headers;
timeout?: number;
onProgress?: (data: OnProgressData) => void;
onDone?: (data: OnDoneData) => void;
onError?: (data: OnErrorData) => void;
onTimeout?: (data: OnErrorData) => void;
onProgress?: (data: OnProgressData<T>) => void;
onDone?: (data: OnDoneData<T>) => void;
onError?: (data: OnErrorData<T>) => void;
onTimeout?: (data: OnErrorData<T>) => void;
};

0 comments on commit 9e46e4e

Please sign in to comment.