Skip to content

Commit 9e46e4e

Browse files
authored
Make useFileUpload generic (#4)
* Make generic * Update readme * Update readme * Ensure item has new startedAt on retry * Bump package
1 parent 28b6f5f commit 9e46e4e

File tree

5 files changed

+52
-49
lines changed

5 files changed

+52
-49
lines changed

README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,22 @@ There is an example app in this repo as shown in the above gif. It is located wi
2121
```tsx
2222
import useFileUpload, { UploadItem } from 'react-native-use-file-upload';
2323

24+
// Used in optional type parameter for useFileUpload
25+
interface Item extends UploadItem {
26+
progress?: number;
27+
}
28+
2429
// ...
25-
const [data, setData] = useState<UploadItem[]>([]);
26-
const { startUpload, abortUpload } = useFileUpload({
30+
const [data, setData] = useState<Item[]>([]);
31+
// The generic type param below for useFileUpload is optional
32+
// and defaults to UploadItem. It should inherit UploadItem.
33+
const { startUpload, abortUpload } = useFileUpload<Item>({
2734
url: 'https://example.com/upload',
2835
field: 'file',
2936
// Below options are optional
3037
method: 'POST',
3138
headers,
32-
timeout: 45000,
39+
timeout: 60000,
3340
onProgress,
3441
onDone,
3542
onError,
@@ -51,7 +58,7 @@ const onPressUpload = async () => {
5158
Start a file upload for a given file. Returns a promise that resolves with `OnDoneData` or rejects with `OnErrorData`.
5259

5360
```ts
54-
// Objects passed to startUpload should have the below shape (UploadItem type)
61+
// Objects passed to startUpload should have the below shape at least (UploadItem type)
5562
startUpload({
5663
name: 'file.jpg',
5764
type: 'image/jpg',
@@ -128,7 +135,7 @@ useFileUpload({ headers });
128135
```ts
129136
// OnProgressData type
130137
{
131-
item: UploadItem;
138+
item: UploadItem; // or a type that inherits UploadItem
132139
event: ProgressEvent<EventTarget>;
133140
};
134141
// event is the XMLHttpRequest progress event object and it's shape is -
@@ -149,7 +156,7 @@ useFileUpload({ headers });
149156
```ts
150157
// OnDoneData type
151158
{
152-
item: UploadItem;
159+
item: UploadItem; // or a type that inherits UploadItem
153160
responseBody: string; // eg "{\"foo\":\"baz\"}" (JSON) or "foo"
154161
responseHeaders: string;
155162
}
@@ -166,7 +173,7 @@ useFileUpload({ headers });
166173
```ts
167174
// onErrorData type
168175
{
169-
item: UploadItem;
176+
item: UploadItem; // or a type that inherits UploadItem
170177
error: string;
171178
}
172179
```
@@ -182,7 +189,7 @@ useFileUpload({ headers });
182189
```ts
183190
// OnErrorData type
184191
{
185-
item: UploadItem;
192+
item: UploadItem; // or a type that inherits UploadItem
186193
error: string;
187194
timeout: boolean; // true here
188195
}

example/src/App.tsx

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function App() {
3737
const [data, setData] = useState<Item[]>([]);
3838
const dragStartAnimatedValue = useRef(new Animated.Value(1));
3939

40-
const { startUpload, abortUpload } = useFileUpload({
40+
const { startUpload, abortUpload } = useFileUpload<Item>({
4141
url: 'http://localhost:8080/upload',
4242
field: 'file',
4343
// optional below
@@ -109,13 +109,7 @@ export default function App() {
109109
});
110110
};
111111

112-
async function onProgress({
113-
item,
114-
event,
115-
}: {
116-
item: Item;
117-
event: OnProgressData['event'];
118-
}) {
112+
async function onProgress({ item, event }: OnProgressData<Item>) {
119113
const progress = event?.loaded
120114
? Math.round((event.loaded / event.total) * 100)
121115
: 0;
@@ -125,16 +119,16 @@ export default function App() {
125119
// This is needed after moving to FastImage?!?!
126120
const now = new Date().getTime();
127121
const elapsed = now - item.startedAt!;
128-
if (progress >= 100 && elapsed <= 200) {
122+
if (progress === 100 && elapsed <= 200) {
129123
for (let i = 0; i <= 100; i += 25) {
130-
updateItem({
131-
item,
132-
keysAndValues: [
133-
{
134-
key: 'progress',
135-
value: i,
136-
},
137-
],
124+
setData((prevState) => {
125+
const newState = [...prevState];
126+
const itemToUpdate = newState.find((s) => s.uri === item.uri);
127+
if (itemToUpdate) {
128+
// item can fail before this hack is done because of the sleep
129+
itemToUpdate.progress = itemToUpdate.failed ? undefined : i;
130+
}
131+
return newState;
138132
});
139133
await sleep(800);
140134
}
@@ -165,7 +159,7 @@ export default function App() {
165159
// :~)
166160
const putItOnTheLine = async (_data: Item[]) => {
167161
const promises = _data
168-
.filter((item) => typeof item.progress !== 'number') // leave out any in progress
162+
.filter((item) => typeof item.progress !== 'number') // leave out any in progress or completed
169163
.map((item) => startUpload(item));
170164
// use Promise.all here if you want an error from a timeout or error
171165
const result = await allSettled(promises);
@@ -215,10 +209,7 @@ export default function App() {
215209
},
216210
],
217211
});
218-
// wrapped in try/catch here just to get rid of possible unhandled promise warning
219-
try {
220-
await startUpload(item);
221-
} catch (_ex) {}
212+
startUpload({ ...item, startedAt: new Date().getTime() }).catch(() => {});
222213
};
223214

224215
const onDragStart = () => {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-use-file-upload",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"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.",
55
"main": "lib/commonjs/index",
66
"module": "lib/module/index",

src/hooks/useFileUpload.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
UploadItem,
77
} from '../types';
88

9-
export default function useFileUpload({
9+
export default function useFileUpload<T extends UploadItem = UploadItem>({
1010
url,
1111
field,
1212
method = 'POST',
@@ -16,12 +16,12 @@ export default function useFileUpload({
1616
onDone,
1717
onError,
1818
onTimeout,
19-
}: FileUploadOptions) {
19+
}: FileUploadOptions<T>) {
2020
const requests = useRef<{
2121
[key: string]: XMLHttpRequest;
2222
}>({});
2323

24-
const startUpload = (item: UploadItem): Promise<OnDoneData | OnErrorData> => {
24+
const startUpload = (item: T): Promise<OnDoneData<T> | OnErrorData<T>> => {
2525
return new Promise((resolve, reject) => {
2626
const formData = new FormData();
2727
formData.append(field, item);
@@ -41,7 +41,7 @@ export default function useFileUpload({
4141
if (timeout) {
4242
xhr.timeout = timeout;
4343
xhr.ontimeout = () => {
44-
const result: OnErrorData = {
44+
const result: OnErrorData<T> = {
4545
item,
4646
error: xhr.responseText,
4747
timeout: true,
@@ -52,7 +52,7 @@ export default function useFileUpload({
5252
}
5353

5454
xhr.onload = () => {
55-
const result: OnDoneData = {
55+
const result: OnDoneData<T> = {
5656
item,
5757
responseBody: xhr.response || xhr.responseText,
5858
responseHeaders: xhr.getAllResponseHeaders(),
@@ -62,7 +62,7 @@ export default function useFileUpload({
6262
};
6363

6464
xhr.onerror = () => {
65-
const result: OnErrorData = {
65+
const result: OnErrorData<T> = {
6666
item,
6767
error: xhr.responseText,
6868
};
@@ -71,7 +71,7 @@ export default function useFileUpload({
7171
};
7272

7373
xhr.onabort = () => {
74-
const result: OnErrorData = {
74+
const result: OnErrorData<T> = {
7575
item,
7676
error: 'Request aborted',
7777
};

src/types.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,37 @@ export type UploadItem = {
44
uri: string;
55
};
66

7-
export type OnProgressData = {
8-
item: UploadItem;
7+
// "T extends UploadItem = UploadItem"
8+
// Generic type parameter that allows passing
9+
// a custom type that inherits UploadItem (constraint).
10+
// It defaults to UploadItem when no type argument is passed.
11+
12+
export type OnProgressData<T extends UploadItem = UploadItem> = {
13+
item: T;
914
event: ProgressEvent<EventTarget>;
1015
};
1116

12-
export type OnDoneData = {
13-
item: UploadItem;
17+
export type OnDoneData<T extends UploadItem = UploadItem> = {
18+
item: T;
1419
responseBody: string;
1520
responseHeaders: string;
1621
};
1722

18-
export type OnErrorData = {
19-
item: UploadItem;
23+
export type OnErrorData<T extends UploadItem = UploadItem> = {
24+
item: T;
2025
error: string;
2126
timeout?: boolean;
2227
};
2328

24-
export type FileUploadOptions = {
29+
export type FileUploadOptions<T extends UploadItem = UploadItem> = {
2530
url: string;
2631
field: string;
2732
// optional below
2833
method?: string;
2934
headers?: Headers;
3035
timeout?: number;
31-
onProgress?: (data: OnProgressData) => void;
32-
onDone?: (data: OnDoneData) => void;
33-
onError?: (data: OnErrorData) => void;
34-
onTimeout?: (data: OnErrorData) => void;
36+
onProgress?: (data: OnProgressData<T>) => void;
37+
onDone?: (data: OnDoneData<T>) => void;
38+
onError?: (data: OnErrorData<T>) => void;
39+
onTimeout?: (data: OnErrorData<T>) => void;
3540
};

0 commit comments

Comments
 (0)