From 4950f7850d57e2e605081f6139da4d6599d91426 Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Mon, 7 Nov 2022 22:36:39 -0600
Subject: [PATCH 1/8] Use FastImage
---
example/globals.d.ts | 1 +
example/ios/Podfile.lock | 29 +++++++++++++++++++++++++++++
example/package.json | 1 +
example/src/App.tsx | 22 ++++++++++++----------
example/src/img/placeholder.png | Bin 0 -> 1775 bytes
example/yarn.lock | 5 +++++
6 files changed, 48 insertions(+), 10 deletions(-)
create mode 100644 example/globals.d.ts
create mode 100644 example/src/img/placeholder.png
diff --git a/example/globals.d.ts b/example/globals.d.ts
new file mode 100644
index 0000000..e2937d4
--- /dev/null
+++ b/example/globals.d.ts
@@ -0,0 +1 @@
+declare module '*.png';
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 6a36ee6..42a7fec 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -75,6 +75,15 @@ PODS:
- glog (0.3.5)
- hermes-engine (0.70.3)
- libevent (2.1.12)
+ - libwebp (1.2.3):
+ - libwebp/demux (= 1.2.3)
+ - libwebp/mux (= 1.2.3)
+ - libwebp/webp (= 1.2.3)
+ - libwebp/demux (1.2.3):
+ - libwebp/webp
+ - libwebp/mux (1.2.3):
+ - libwebp/demux
+ - libwebp/webp (1.2.3)
- OpenSSL-Universal (1.1.1100)
- RCT-Folly (2021.07.22.00):
- boost
@@ -370,8 +379,18 @@ PODS:
- React-jsi (= 0.70.3)
- React-logger (= 0.70.3)
- React-perflogger (= 0.70.3)
+ - RNFastImage (8.6.3):
+ - React-Core
+ - SDWebImage (~> 5.11.1)
+ - SDWebImageWebPCoder (~> 0.8.4)
- RNReactNativeHapticFeedback (1.14.0):
- React-Core
+ - SDWebImage (5.11.1):
+ - SDWebImage/Core (= 5.11.1)
+ - SDWebImage/Core (5.11.1)
+ - SDWebImageWebPCoder (0.8.5):
+ - libwebp (~> 1.0)
+ - SDWebImage/Core (~> 5.10)
- SocketRocket (0.6.0)
- Yoga (1.14.0)
- YogaKit (1.18.1):
@@ -437,6 +456,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+ - RNFastImage (from `../node_modules/react-native-fast-image`)
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -454,7 +474,10 @@ SPEC REPOS:
- FlipperKit
- fmt
- libevent
+ - libwebp
- OpenSSL-Universal
+ - SDWebImage
+ - SDWebImageWebPCoder
- SocketRocket
- YogaKit
@@ -527,6 +550,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
+ RNFastImage:
+ :path: "../node_modules/react-native-fast-image"
RNReactNativeHapticFeedback:
:path: "../node_modules/react-native-haptic-feedback"
Yoga:
@@ -551,6 +576,7 @@ SPEC CHECKSUMS:
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: bb344d89a0d14c2c91ad357480a79698bb80e186
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
+ libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCTRequired: 5cf7e7d2f12699724b59f90350257a422eaa9492
@@ -580,7 +606,10 @@ SPEC CHECKSUMS:
React-RCTVibration: b9a58ffdd18446f43d493a4b0ecd603ee86be847
React-runtimeexecutor: e9b1f9310158a1e265bcdfdfd8c62d6174b947a2
ReactCommon: 01064177e66d652192c661de899b1076da962fd9
+ RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8
RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
+ SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
+ SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: 2ed968a4f060a92834227c036279f2736de0fce3
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
diff --git a/example/package.json b/example/package.json
index dd52f2f..e2753a8 100644
--- a/example/package.json
+++ b/example/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"react": "18.1.0",
"react-native": "0.70.3",
+ "react-native-fast-image": "8.6.3",
"react-native-haptic-feedback": "1.14.0",
"react-native-image-picker": "4.10.0",
"react-native-sortable-grid": "https://github.com/rossmartin/react-native-sortable-grid.git#b5c911c263b8c230c4973af00986724bcb234929"
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 5cfa072..64ca69d 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -3,7 +3,6 @@ import {
ActivityIndicator,
Animated,
Button,
- ImageBackground,
Pressable,
SafeAreaView,
StyleSheet,
@@ -15,10 +14,12 @@ import SortableGrid, { ItemOrder } from 'react-native-sortable-grid';
import ReactNativeHapticFeedback, {
HapticOptions,
} from 'react-native-haptic-feedback';
+import FastImage from 'react-native-fast-image';
import ProgressBar from './components/ProgressBar';
import useFileUpload, { UploadItem } from '../../src/index';
import { allSettled } from './util/allSettled';
+import placeholderImage from './img/placeholder.png';
const hapticFeedbackOptions: HapticOptions = {
enableVibrateFallback: false,
@@ -114,7 +115,6 @@ export default function App() {
const response = await launchImageLibrary({
mediaType: 'photo',
selectionLimit: 0,
- quality: 0.8,
});
const items: Item[] =
@@ -215,12 +215,13 @@ export default function App() {
const showProgress = !item.failed && itemProgress > 0 && itemProgress < 100;
return (
-
+
+
{showProgress ? (
) : null}
@@ -233,7 +234,7 @@ export default function App() {
✗
-
+
);
};
@@ -271,11 +272,12 @@ const styles = StyleSheet.create({
},
imageBackground: {
flex: 1,
+ margin: 8,
justifyContent: 'center',
alignItems: 'center',
- margin: 8,
},
image: {
+ ...StyleSheet.absoluteFillObject,
borderRadius: 12,
},
deleteIcon: {
diff --git a/example/src/img/placeholder.png b/example/src/img/placeholder.png
new file mode 100644
index 0000000000000000000000000000000000000000..df321bb2b54d58325609003e072d916508a44b61
GIT binary patch
literal 1775
zcmeAS@N?(olHy`uVBq!ia0vp^%Yk?S2OE&g6s`$jU|@{TbaoE#baqwWn
z(3n^{!O@#3P~iCg=&lK(PO_(z)B(TSSk*nanFNW|NXd9L60ISL4t
z@HV~Up1bEOW7i_Sxr_7z-Yk7$>c9HxqDr3s^7U!Yc$rIDOpf`;HxwP+cFFtm#NIb)
zDj%8@m|hNSYOx+O=xg3YPn~jlq4u6d)0FGf`d;bQ
zEo?pgY~9DL%{TU0$48!?(5BhGvB7o83)cyXlMH<&)u(Iyd+BZRYtjwI!;Csp0vrU*
zd;?A-8cbA=b7W;p>JU0TW66}Gd+YCC{QT`+!_3HEtB!x(wvO>Rlf;MWdp86wSaZa*
z&P>a+zdki4zSLUJ-0}s(zvOfA*BcL51nhtQ=kCKjb$lguOs3z3FA61T9b
zrzemOPA*DK%`48xFZsulxB_U@wak!+k_cZPtK|G#y~LFKq*T3%+yamm2Ac{iATu|$
zBvGLvHz%*ys=`(YtilS&1_|pcDS(xfWZNo5_y#CA=NF|anCThl87SFtDJUq|6s4qD
z1-ZCEwF7y!N*N_31y=g{<>lpi<;HsXMd|v6mX?EaW<_dFq)TQlFnEA=*cqA`*nrhz$RKP)(iwrQ(;QhRk_@`e(Bjl0=loot
zl_hqFn1O0WmV|1w(Fge)DI_4l3>F23vmKX>K0MCsxY#`MW&@K5yQhm|NCo5DOOCt@
z3IZ&K2L$;4WH+;jetw~O<4ozh+IIz9EZl1zs7)66!TNwjF`D7rn{1xu$&ERT7Ms@_
z^7yfBVtsIC-!TIV$rEA?#=oTwKS)s+RWup|Ln{?*V6RxkbivYW$6HX*=IQF^vd$@?
F2>`IysG9%)
literal 0
HcmV?d00001
diff --git a/example/yarn.lock b/example/yarn.lock
index c6fe38b..21263de 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -3743,6 +3743,11 @@ react-native-codegen@^0.70.5:
jscodeshift "^0.13.1"
nullthrows "^1.1.1"
+react-native-fast-image@8.6.3:
+ version "8.6.3"
+ resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-8.6.3.tgz#6edc3f9190092a909d636d93eecbcc54a8822255"
+ integrity sha512-Sdw4ESidXCXOmQ9EcYguNY2swyoWmx53kym2zRsvi+VeFCHEdkO+WG1DK+6W81juot40bbfLNhkc63QnWtesNg==
+
react-native-gradle-plugin@^0.70.3:
version "0.70.3"
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
From 5bc8f58a10f195c51f8bc919cfd304ae58fb5479 Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 19:36:37 -0600
Subject: [PATCH 2/8] Update readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e4b8559..2c9b09d 100644
--- a/README.md
+++ b/README.md
@@ -199,7 +199,7 @@ useFileUpload({ headers });
Requests will time out if you background the app. This can be addressed by using [react-native-background-upload](https://github.com/Vydia/react-native-background-upload).
-The React Native team did a a heavy lift to polyfill and bridge `XMLHttpRequest` to the native side for us. Hopefully some day it is updated to support requests while an app is backgrounded.
+The React Native team did a a heavy lift to polyfill and bridge `XMLHttpRequest` to the native side for us. [There is an open PR in React Native to allow network requests to run in the background for iOS](https://github.com/facebook/react-native/pull/31838). There are plans to have a similar PR for Android as well. `react-native-background-upload` is great but if backgrounding can be supported without any native dependencies it is a win for everyone.
### Why send 1 file at a time instead of multiple in a single request?
From e53d0e9cc8532fb0afb3b5443d16e31faf1e501f Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 19:44:08 -0600
Subject: [PATCH 3/8] Reject promise when request is aborted
---
src/hooks/useFileUpload.ts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/hooks/useFileUpload.ts b/src/hooks/useFileUpload.ts
index d01aabf..557d0ab 100644
--- a/src/hooks/useFileUpload.ts
+++ b/src/hooks/useFileUpload.ts
@@ -70,6 +70,15 @@ export default function useFileUpload({
reject(result);
};
+ xhr.onabort = () => {
+ const result: OnErrorData = {
+ item,
+ error: 'Request aborted',
+ };
+ onError?.(result);
+ reject(result);
+ };
+
headers?.forEach((value: string, key: string) => {
xhr.setRequestHeader(key, value);
});
From 5d535945a796ecf1eb8c3722e01949f60c369aee Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 20:01:08 -0600
Subject: [PATCH 4/8] Nit for progress
---
example/src/App.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 64ca69d..0619dd5 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -44,7 +44,7 @@ export default function App() {
timeout: 60000, // you can set this lower to cause timeouts to happen
onProgress: ({ item, event }) => {
const progress = event?.loaded
- ? Math.floor((event.loaded / event.total) * 100)
+ ? Math.round((event.loaded / event.total) * 100)
: 0;
updateItem({
item,
From 1fba6c2c487beb50a22fe9c910409df2c8f6d453 Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 20:54:37 -0600
Subject: [PATCH 5/8] Update readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2c9b09d..b7487c6 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ startUpload({
### `abortUpload`
-Abort a file upload for a given file.
+Abort a file upload for a given file. The promise from `startUpload` gets rejected and `onError` runs if present.
```ts
// Pass the uri of a file that started uploading
From 589b0d7f31ac653b6e0f7799daaa77dcafcb8177 Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 21:41:17 -0600
Subject: [PATCH 6/8] Update example for FastImage
---
example/src/App.tsx | 99 ++++++++++++++-----
.../src/util/{allSettled.ts => general.ts} | 3 +
2 files changed, 77 insertions(+), 25 deletions(-)
rename example/src/util/{allSettled.ts => general.ts} (73%)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 0619dd5..5d3f42b 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -17,8 +17,8 @@ import ReactNativeHapticFeedback, {
import FastImage from 'react-native-fast-image';
import ProgressBar from './components/ProgressBar';
-import useFileUpload, { UploadItem } from '../../src/index';
-import { allSettled } from './util/allSettled';
+import useFileUpload, { UploadItem, OnProgressData } from '../../src/index';
+import { allSettled, sleep } from './util/general';
import placeholderImage from './img/placeholder.png';
const hapticFeedbackOptions: HapticOptions = {
@@ -26,10 +26,11 @@ const hapticFeedbackOptions: HapticOptions = {
ignoreAndroidSystemSettings: false,
};
-interface Item extends UploadItem {
+export interface Item extends UploadItem {
progress?: number;
failed?: boolean; // true on timeout or error
- completed?: boolean; // true when request is done
+ completedAt?: number; // when request is done
+ startedAt?: number; // when request starts
}
export default function App() {
@@ -42,20 +43,17 @@ export default function App() {
// optional below
method: 'POST',
timeout: 60000, // you can set this lower to cause timeouts to happen
- onProgress: ({ item, event }) => {
- const progress = event?.loaded
- ? Math.round((event.loaded / event.total) * 100)
- : 0;
- updateItem({
- item,
- keysAndValues: [{ key: 'progress', value: progress }],
- });
- },
+ onProgress,
onDone: (_data) => {
//console.log('onDone, data: ', data);
updateItem({
item: _data.item,
- keysAndValues: [{ key: 'completed', value: true }],
+ keysAndValues: [
+ {
+ key: 'completedAt',
+ value: new Date().getTime(),
+ },
+ ],
});
},
onError: (_data) => {
@@ -111,6 +109,43 @@ export default function App() {
});
};
+ async function onProgress({
+ item,
+ event,
+ }: {
+ item: Item;
+ event: OnProgressData['event'];
+ }) {
+ const progress = event?.loaded
+ ? Math.round((event.loaded / event.total) * 100)
+ : 0;
+
+ // This logic before the else below is a hack to
+ // simulate progress for ones that upload immediately.
+ // This is needed after moving to FastImage?!?!
+ const now = new Date().getTime();
+ const elapsed = now - item.startedAt!;
+ if (progress >= 100 && elapsed <= 200) {
+ for (let i = 0; i <= 100; i += 25) {
+ updateItem({
+ item,
+ keysAndValues: [
+ {
+ key: 'progress',
+ value: i,
+ },
+ ],
+ });
+ await sleep(800);
+ }
+ } else {
+ updateItem({
+ item,
+ keysAndValues: [{ key: 'progress', value: progress }],
+ });
+ }
+ }
+
const onPressSelectMedia = async () => {
const response = await launchImageLibrary({
mediaType: 'photo',
@@ -127,16 +162,9 @@ export default function App() {
setData((prevState) => [...prevState, ...items]);
};
- const onPressUpload = async () => {
- // allow uploading any that previously failed
- setData((prevState) =>
- [...prevState].map((item) => ({
- ...item,
- failed: false,
- }))
- );
-
- const promises = data
+ // :~)
+ const putItOnTheLine = async (_data: Item[]) => {
+ const promises = _data
.filter((item) => typeof item.progress !== 'number') // leave out any in progress
.map((item) => startUpload(item));
// use Promise.all here if you want an error from a timeout or error
@@ -144,6 +172,21 @@ export default function App() {
console.log('result: ', result);
};
+ const onPressUpload = async () => {
+ // allow uploading any that previously failed
+ setData((prevState) => {
+ const newState = [...prevState].map((item) => ({
+ ...item,
+ failed: false,
+ startedAt: new Date().getTime(),
+ }));
+
+ putItOnTheLine(newState);
+
+ return newState;
+ });
+ };
+
const onPressDeleteItem = (item: Item) => () => {
setData((prevState) => {
const newState = [...prevState];
@@ -166,6 +209,10 @@ export default function App() {
key: 'failed',
value: false,
},
+ {
+ key: 'startedAt',
+ value: new Date().getTime(),
+ },
],
});
// wrapped in try/catch here just to get rid of possible unhandled promise warning
@@ -230,7 +277,9 @@ export default function App() {
↻
) : null}
- {item.completed ? ✓ : null}
+ {item.completedAt ? (
+ ✓
+ ) : null}
✗
diff --git a/example/src/util/allSettled.ts b/example/src/util/general.ts
similarity index 73%
rename from example/src/util/allSettled.ts
rename to example/src/util/general.ts
index e4f736c..0ddbb3e 100644
--- a/example/src/util/allSettled.ts
+++ b/example/src/util/general.ts
@@ -1,3 +1,6 @@
+export const sleep = (time: number) =>
+ new Promise((resolve) => setTimeout(resolve, time));
+
export const allSettled = (promises: Promise[]) => {
return Promise.all(
promises.map((promise) =>
From 66c5797af4d58a2775a8837dac9c1f7f0e1b6c62 Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 21:51:06 -0600
Subject: [PATCH 7/8] Bump package
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 4592f66..feb3031 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-use-file-upload",
- "version": "0.1.1",
+ "version": "0.1.2",
"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",
From 30831c05cc866fc05ac58b51db65c52707399ceb Mon Sep 17 00:00:00 2001
From: Ross Martin <2498502+rossmartin@users.noreply.github.com>
Date: Tue, 8 Nov 2022 21:55:49 -0600
Subject: [PATCH 8/8] Update comment
---
example/src/App.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 5d3f42b..6bed716 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -26,7 +26,7 @@ const hapticFeedbackOptions: HapticOptions = {
ignoreAndroidSystemSettings: false,
};
-export interface Item extends UploadItem {
+interface Item extends UploadItem {
progress?: number;
failed?: boolean; // true on timeout or error
completedAt?: number; // when request is done
@@ -121,7 +121,7 @@ export default function App() {
: 0;
// This logic before the else below is a hack to
- // simulate progress for ones that upload immediately.
+ // simulate progress for any that upload immediately.
// This is needed after moving to FastImage?!?!
const now = new Date().getTime();
const elapsed = now - item.startedAt!;