diff --git a/README.md b/README.md index b7487c6..71aa359 100644 --- a/README.md +++ b/README.md @@ -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([]); -const { startUpload, abortUpload } = useFileUpload({ +const [data, setData] = useState([]); +// The generic type param below for useFileUpload is optional +// and defaults to UploadItem. It should inherit UploadItem. +const { startUpload, abortUpload } = useFileUpload({ url: 'https://example.com/upload', field: 'file', // Below options are optional method: 'POST', headers, - timeout: 45000, + timeout: 60000, onProgress, onDone, onError, @@ -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', @@ -128,7 +135,7 @@ useFileUpload({ headers }); ```ts // OnProgressData type { - item: UploadItem; + item: UploadItem; // or a type that inherits UploadItem event: ProgressEvent; }; // event is the XMLHttpRequest progress event object and it's shape is - @@ -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; } @@ -166,7 +173,7 @@ useFileUpload({ headers }); ```ts // onErrorData type { - item: UploadItem; + item: UploadItem; // or a type that inherits UploadItem error: string; } ``` @@ -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 } diff --git a/example/src/App.tsx b/example/src/App.tsx index 6bed716..a3e7358 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -37,7 +37,7 @@ export default function App() { const [data, setData] = useState([]); const dragStartAnimatedValue = useRef(new Animated.Value(1)); - const { startUpload, abortUpload } = useFileUpload({ + const { startUpload, abortUpload } = useFileUpload({ url: 'http://localhost:8080/upload', field: 'file', // optional below @@ -109,13 +109,7 @@ export default function App() { }); }; - async function onProgress({ - item, - event, - }: { - item: Item; - event: OnProgressData['event']; - }) { + async function onProgress({ item, event }: OnProgressData) { const progress = event?.loaded ? Math.round((event.loaded / event.total) * 100) : 0; @@ -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); } @@ -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); @@ -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 = () => { diff --git a/package.json b/package.json index feb3031..5af6e79 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/hooks/useFileUpload.ts b/src/hooks/useFileUpload.ts index 557d0ab..2d3ce89 100644 --- a/src/hooks/useFileUpload.ts +++ b/src/hooks/useFileUpload.ts @@ -6,7 +6,7 @@ import type { UploadItem, } from '../types'; -export default function useFileUpload({ +export default function useFileUpload({ url, field, method = 'POST', @@ -16,12 +16,12 @@ export default function useFileUpload({ onDone, onError, onTimeout, -}: FileUploadOptions) { +}: FileUploadOptions) { const requests = useRef<{ [key: string]: XMLHttpRequest; }>({}); - const startUpload = (item: UploadItem): Promise => { + const startUpload = (item: T): Promise | OnErrorData> => { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append(field, item); @@ -41,7 +41,7 @@ export default function useFileUpload({ if (timeout) { xhr.timeout = timeout; xhr.ontimeout = () => { - const result: OnErrorData = { + const result: OnErrorData = { item, error: xhr.responseText, timeout: true, @@ -52,7 +52,7 @@ export default function useFileUpload({ } xhr.onload = () => { - const result: OnDoneData = { + const result: OnDoneData = { item, responseBody: xhr.response || xhr.responseText, responseHeaders: xhr.getAllResponseHeaders(), @@ -62,7 +62,7 @@ export default function useFileUpload({ }; xhr.onerror = () => { - const result: OnErrorData = { + const result: OnErrorData = { item, error: xhr.responseText, }; @@ -71,7 +71,7 @@ export default function useFileUpload({ }; xhr.onabort = () => { - const result: OnErrorData = { + const result: OnErrorData = { item, error: 'Request aborted', }; diff --git a/src/types.ts b/src/types.ts index ba2c341..6b3dfc3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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 = { + item: T; event: ProgressEvent; }; -export type OnDoneData = { - item: UploadItem; +export type OnDoneData = { + item: T; responseBody: string; responseHeaders: string; }; -export type OnErrorData = { - item: UploadItem; +export type OnErrorData = { + item: T; error: string; timeout?: boolean; }; -export type FileUploadOptions = { +export type FileUploadOptions = { 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) => void; + onDone?: (data: OnDoneData) => void; + onError?: (data: OnErrorData) => void; + onTimeout?: (data: OnErrorData) => void; };