Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};