-
Notifications
You must be signed in to change notification settings - Fork 411
Description
Provide environment information
el_badi_dev@ELs-MacBook-Air birinia % npx envinfo --system --binaries --browsers --npmPackages "typescript,uploadthing,@uploadthing/react,@uploadthing/solid"
Need to install the following packages:
envinfo@7.21.0
Ok to proceed? (y) y
System:
OS: macOS 26.2
CPU: (8) arm64 Apple M1
Memory: 219.23 MB / 8.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.4.1 - /Users/el_badi_dev/.nvm/versions/node/v24.4.1/bin/node
npm: 11.4.2 - /Users/el_badi_dev/.nvm/versions/node/v24.4.1/bin/npm
pnpm: 10.12.1 - /opt/homebrew/bin/pnpm
bun: 1.0.35 - /opt/homebrew/bin/bun
Watchman: 2025.09.15.00 - /opt/homebrew/bin/watchman
Browsers:
Chrome: 146.0.7680.81
Edge: 146.0.3856.62
Safari: 26.2
npmPackages:
typescript: ~5.9.2 => 5.9.3
uploadthing: ^7.7.3 => 7.7.3
el_badi_dev@ELs-MacBook-Air birinia %Describe the bug
🐛 [expo] Network request failed on Expo SDK 55 / React Native 0.83 — onBeforeUploadBegin never called
Description
After upgrading from Expo SDK 53 (RN 0.79) to Expo SDK 55 (RN 0.83), all uploads fail immediately with a Network request failed error. The crash happens before onBeforeUploadBegin is ever called, meaning the failure occurs during the initial presigned URL request — before the upload pipeline even starts.
The same codebase, same uploadthing versions, same backend — works perfectly on SDK 53.
Error
ERROR [Error: Uncaught (in promise, id: 0) TypeError: Network request failed] Code: fetch.umd.js 565 | xhr.onerror = function() { 566 | setTimeout(function() { > 567 | reject(new TypeError('Network request failed')); | ^ 568 | }, 0); 569 | };
Call Stack:
setTimeout$argument_0 (node_modules/whatwg-fetch/dist/fetch.umd.js:567:31)
Reproduction
- Working project on Expo SDK 53 / RN 0.79 with
@uploadthing/expo - Upgrade to Expo SDK 55 / RN 0.83 (New Architecture mandatory, no opt-out)
- Trigger
openImagePicker onBeforeUploadBeginis never called — crash happens before it
Environment
| SDK 53 (working) | SDK 55 (broken) | |
|---|---|---|
| expo | ^53.0.20 | ^55.0.6 |
| react-native | 0.79.5 | 0.83.2 |
| @uploadthing/expo | 7.2.5 | 7.2.6 |
| uploadthing | 7.7.3 | 7.7.4 |
| expo-image-picker | ~16.1.4 | ~55.0.12 |
| Architecture | Old (opt-in new) | New Architecture only |
Backend: Convex HTTP action (https://xxx.convex.site)
Platform: Android (physical device), both WiFi and mobile data
What was tried
- ✅
@bacons/text-decoderpolyfill - ✅
expo/fetchpolyfill viapolyfillGlobal - ✅ Blocking
whatwg-fetchin metroresolveRequest - ✅ Blocking specifically when origin is
Libraries/Network/fetch.js - ✅ Downgrading
expo-image-pickerandexpo-document-pickerto SDK 53 versions - ✅ Simplifying
onBeforeUploadBeginto just pass the file through unchanged - ✅ Removing
react-native-compressorentirely - ✅ Verified
EXPO_PUBLIC_SERVER_URLis correct (https://xxx.convex.site)
Key finding
react-native/Libraries/Network/fetch.js does require('whatwg-fetch') in both RN 0.79 and RN 0.83 — so this is not a new change. The whatwg-fetch polyfill was present and working in SDK 53, but causes Network request failed in SDK 55 / RN 0.83 New Architecture (Bridgeless mode).
// node_modules/react-native/Libraries/Network/fetch.js
'use strict';
require('whatwg-fetch'); // <-- present in both RN 0.79 and RN 0.83
export const fetch = global.fetch;
...
This suggests the issue is specifically with how Bridgeless / New Architecture in RN 0.83 handles XHR-based requests from whatwg-fetch when uploadthing makes the initial presigned URL fetch.
Question
Is @uploadthing/expo tested against Expo SDK 55 / RN 0.83 with New Architecture (Bridgeless)? Is there a known workaround or fix in progress?
Link to reproduction
https//:google.com
To reproduce
Steps to Reproduce
- Set up a React Native / Expo project using Expo SDK 55 (React Native 0.83, New Architecture mandatory)
- Install uploadthing packages:
npm install uploadthing @uploadthing/expo expo-image-picker expo-document-picker
- Set up a Convex HTTP action as the uploadthing backend and set
EXPO_PUBLIC_SERVER_URL=https://xxx.convex.site - Generate helpers in
uploadthing.ts:import { generateReactNativeHelpers } from "@uploadthing/expo"; export const { useImageUploader } = generateReactNativeHelpers({ url: process.env.EXPO_PUBLIC_SERVER_URL, });
- Use
useImageUploaderin a component and callopenImagePicker - Add a
console.loginsideonBeforeUploadBeginto verify it is called - Run on a physical Android device and trigger the image picker
- Expected:
onBeforeUploadBeginis called, upload proceeds - Actual: App crashes with
TypeError: Network request failedfromwhatwg-fetch/dist/fetch.umd.js:567—onBeforeUploadBeginis never reached
Additional information
Additional Context — Uploadthing Implementation Files
uploadthing.ts (client helper setup)
import { generateReactNativeHelpers } from "@uploadthing/expo";
import { OurFileRouter } from "~/convex/uploadthing/index";
export const { useImageUploader, useDocumentUploader } =
generateReactNativeHelpers<OurFileRouter>({
/**
* Your server url.
* @default process.env.EXPO_PUBLIC_SERVER_URL
* @remarks In dev we will also try to use Expo.debuggerHost
*/
url: process.env.EXPO_PUBLIC_SERVER_URL,
});Note: Backend is a Convex HTTP action, not Expo API routes.
EXPO_PUBLIC_SERVER_URLis set tohttps://xxx.convex.site.
photo-picker.tsx (upload component)
import { useAuthToken } from "@convex-dev/auth/react";
import { openSettings } from "expo-linking";
import React from "react";
import { Alert } from "react-native";
import { PhotoIcon } from "react-native-heroicons/outline";
import { useImageUploader } from "~/src/lib/uploadthing";
import { Button } from "../ui/button";
import { useTheme } from "~/src/hooks/use-theme";
interface PhotoPickerProps {
onImageSelected: (localUri: string) => string;
onUploadComplete: (imageId: string, uploadedUrl: string) => void;
onUploadError: (imageId: string, error: string) => void;
}
const PhotoPicker = ({
onImageSelected,
onUploadComplete,
onUploadError,
}: PhotoPickerProps) => {
const { theme } = useTheme();
const token = useAuthToken();
let currentImageId: string | null = null;
const { openImagePicker } = useImageUploader("imageUploader", {
headers: (): Record<string, string> => {
if (!token) return {};
return { Authorization: `Bearer ${token}` };
},
onClientUploadComplete: async (e) => {
if (e[0].ufsUrl && e[0].customId && currentImageId) {
const urlParts = e[0].ufsUrl.split("/");
urlParts.pop();
const baseUrl = urlParts.join("/");
const imageUrl = `${baseUrl}/${e[0].customId}`;
onUploadComplete(currentImageId, imageUrl);
currentImageId = null;
}
},
// ⚠️ This callback is NEVER reached — crash happens before it
onBeforeUploadBegin: async (files: any[]) => {
const file = files[0];
currentImageId = onImageSelected(file.uri);
return [file];
},
onUploadError: (error) => {
if (currentImageId) {
onUploadError(currentImageId, error.message || "فشل في رفع الصورة");
currentImageId = null;
}
Alert.alert("خطأ في الرفع", error.message || "فشل في رفع الصورة");
},
});
const handlePress = () => {
openImagePicker({
source: "library",
onInsufficientPermissions: () => {
Alert.alert(
"لا توجد صلاحيات",
"تحتاج إلى منح صلاحية للوصول إلى الصور لاستخدام هذه الميزة",
[{ text: "إلغاء" }, { text: "فتح الإعدادات", onPress: openSettings }]
);
},
});
};
return (
<Button
onPress={handlePress}
variant="outline"
className="w-20 h-20 rounded-xl border-2 border-dashed border-gray-300 bg-gray-50"
>
<PhotoIcon size={24} color={theme.foreground} />
</Button>
);
};
export default PhotoPicker;Key observations
onBeforeUploadBeginis never called — confirmed via console log- The crash is in
whatwg-fetch/dist/fetch.umd.jsat the XHRonerrorhandler - This means the failure is in the initial presigned URL request to the Convex backend, before the upload pipeline starts
- The same code works on SDK 53 / RN 0.79 with no changes
👨👧👦 Contributing
- 🙋♂️ Yes, I'd be down to file a PR fixing this bug!
Code of Conduct
- I agree to follow this project's Code of Conduct