fix(packages/nhost-js): react native needs special treatment when using FormData (#3697)

Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
This commit is contained in:
David Barroso
2025-11-17 08:37:26 +01:00
committed by GitHub
parent 5b98f4ece2
commit 6c044458ca
9 changed files with 140 additions and 32 deletions

View File

@@ -3,8 +3,14 @@ table:
schema: auth
is_enum: true
configuration:
column_config: {}
custom_column_names: {}
column_config:
comment:
custom_name: comment
value:
custom_name: value
custom_column_names:
comment: comment
value: value
custom_name: authRefreshTokenTypes
custom_root_fields:
delete: deleteAuthRefreshTokenTypes

View File

@@ -31,7 +31,7 @@ httpPoolSize = 100
version = 22
[auth]
version = '0.41.1'
version = '0.43.1'
[auth.elevatedPrivileges]
mode = 'disabled'
@@ -183,7 +183,7 @@ capacity = 1
[provider]
[storage]
version = '0.8.0-beta5'
version = '0.9.1'
[observability]
[observability.grafana]

View File

@@ -154,6 +154,11 @@ export default function Files() {
const response = await nhost.storage.uploadFiles({
"bucket-id": "personal",
"file[]": [file as File],
"metadata[]": [
{
metadata: { key1: "value1" },
},
],
});
// Get the processed file data

View File

@@ -467,7 +467,12 @@ export default function Todos() {
)}
{showAddForm && (
<View style={[commonStyles.card, { marginHorizontal: 16, width: undefined }]}>
<View
style={[
commonStyles.card,
{ marginHorizontal: 16, width: undefined },
]}
>
<Text style={commonStyles.cardTitle}>Add New Todo</Text>
<View style={commonStyles.formFields}>
<View style={commonStyles.fieldGroup}>

View File

@@ -17,9 +17,11 @@
"expo-crypto": "14",
"expo-document-picker": "13",
"expo-file-system": "18",
"expo-linking": "^8.0.8",
"expo-router": "~6",
"expo-sharing": "13",
"expo-status-bar": "~3.0.8",
"metro-minify-terser": "^0.83.3",
"react": "19.1.0",
"react-native": "0.81.4"
},

View File

@@ -32,6 +32,9 @@ importers:
expo-file-system:
specifier: '18'
version: 18.1.11(expo@54.0.9)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))
expo-linking:
specifier: ^8.0.8
version: 8.0.8(expo@54.0.9)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
expo-router:
specifier: ~6
version: 6.0.7(@expo/metro-runtime@6.1.2)(@types/react@19.1.13)(expo-constants@18.0.9)(expo-linking@8.0.8)(expo@54.0.9)(react-dom@19.1.1(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
@@ -41,6 +44,9 @@ importers:
expo-status-bar:
specifier: ~3.0.8
version: 3.0.8(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
metro-minify-terser:
specifier: ^0.83.3
version: 0.83.3
react:
specifier: 19.1.0
version: 19.1.0
@@ -2232,6 +2238,10 @@ packages:
resolution: {integrity: sha512-zvIxnh7U0JQ7vT4quasKsijId3dOAWgq+ip2jF/8TMrPUqQabGrs04L2dd0haQJ+PA+d4VvK/bPOY8X/vL2PWw==}
engines: {node: '>=20.19.4'}
metro-minify-terser@0.83.3:
resolution: {integrity: sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==}
engines: {node: '>=20.19.4'}
metro-resolver@0.83.1:
resolution: {integrity: sha512-t8j46kiILAqqFS5RNa+xpQyVjULxRxlvMidqUswPEk5nQVNdlJslqizDm/Et3v/JKwOtQGkYAQCHxP1zGStR/g==}
engines: {node: '>=20.19.4'}
@@ -5840,6 +5850,11 @@ snapshots:
flow-enums-runtime: 0.0.6
terser: 5.44.0
metro-minify-terser@0.83.3:
dependencies:
flow-enums-runtime: 0.0.6
terser: 5.44.0
metro-resolver@0.83.1:
dependencies:
flow-enums-runtime: 0.0.6

View File

@@ -632,16 +632,27 @@ export const createAPIClient = (
): Promise<FetchResponse<UploadFilesResponse201>> => {
const url = `${baseURL}/files`;
const formData = new FormData();
const isReactNative =
typeof navigator !== "undefined" &&
(navigator as { product?: string }).product === "ReactNative";
if (body["bucket-id"] !== undefined) {
formData.append("bucket-id", body["bucket-id"]);
}
if (body["metadata[]"] !== undefined) {
body["metadata[]"].forEach((value) => {
formData.append(
"metadata[]",
new Blob([JSON.stringify(value)], { type: "application/json" }),
"",
);
if (isReactNative) {
formData.append("metadata[]", {
string: JSON.stringify(value),
type: "application/json",
name: "",
} as unknown as Blob);
} else {
formData.append(
"metadata[]",
new Blob([JSON.stringify(value)], { type: "application/json" }),
"",
);
}
});
}
if (body["file[]"] !== undefined) {
@@ -799,14 +810,25 @@ export const createAPIClient = (
): Promise<FetchResponse<FileMetadata>> => {
const url = `${baseURL}/files/${id}`;
const formData = new FormData();
const isReactNative =
typeof navigator !== "undefined" &&
(navigator as { product?: string }).product === "ReactNative";
if (body["metadata"] !== undefined) {
formData.append(
"metadata",
new Blob([JSON.stringify(body["metadata"])], {
if (isReactNative) {
formData.append("metadata", {
string: JSON.stringify(body["metadata"]),
type: "application/json",
}),
"",
);
name: "",
} as unknown as Blob);
} else {
formData.append(
"metadata",
new Blob([JSON.stringify(body["metadata"])], {
type: "application/json",
}),
"",
);
}
}
if (body["file"] !== undefined) {
formData.append("file", body["file"]);

View File

@@ -767,16 +767,30 @@ export const createAPIClient = (
): Promise<FetchResponse<UploadFilesResponse201>> => {
const url = `${ baseURL }/files/`;
const formData = new FormData();
const isReactNative =
typeof navigator !== "undefined" &&
(navigator as { product?: string }).product === "ReactNative";
if (body["bucket-id"] !== undefined) {
formData.append("bucket-id", body["bucket-id"]);
}
if (body["metadata[]"] !== undefined) {
body["metadata[]"].forEach((value) => {
formData.append(
if (isReactNative) {
formData.append(
"metadata[]",
{
string: JSON.stringify(value),
type: "application/json",
name: "",
} as unknown as Blob,
);
} else {
formData.append(
"metadata[]",
new Blob([JSON.stringify(value)], { type: "application/json" }),
"",
)
new Blob([JSON.stringify(value)], { type: "application/json" }),
"",
);
}
}
);
}
@@ -912,12 +926,26 @@ export const createAPIClient = (
): Promise<FetchResponse<FileMetadata>> => {
const url = `${ baseURL }/files/${id}`;
const formData = new FormData();
const isReactNative =
typeof navigator !== "undefined" &&
(navigator as { product?: string }).product === "ReactNative";
if (body["metadata"] !== undefined) {
formData.append(
if (isReactNative) {
formData.append(
"metadata",
{
string: JSON.stringify(body["metadata"]),
type: "application/json",
name: "",
} as unknown as Blob,
);
} else {
formData.append(
"metadata",
new Blob([JSON.stringify(body["metadata"])], { type: "application/json" }),
"",
);
new Blob([JSON.stringify(body["metadata"])], { type: "application/json" }),
"",
);
}
}
if (body["file"] !== undefined) {
formData.append("file", body["file"]);

View File

@@ -126,6 +126,9 @@ export const createAPIClient = (
});
{{- else if .RequestFormData }}
const formData = new FormData();
const isReactNative =
typeof navigator !== "undefined" &&
(navigator as { product?: string }).product === "ReactNative";
{{- range .RequestFormData.Properties }}
{{- if eq .Type.Kind "scalar" }}
@@ -138,11 +141,22 @@ export const createAPIClient = (
{{- if eq .Type.Item.Kind "scalar" }}
formData.append("{{ .Name }}", value)
{{- else if eq .Type.Item.Kind "object" }}
formData.append(
if (isReactNative) {
formData.append(
"{{ .Name }}",
{
string: JSON.stringify(value),
type: "application/json",
name: "",
} as unknown as Blob,
);
} else {
formData.append(
"{{ .Name }}",
new Blob([JSON.stringify(value)], { type: "application/json" }),
"",
)
new Blob([JSON.stringify(value)], { type: "application/json" }),
"",
);
}
{{- else }}
TODO {{ .Type.Kind }} {{ .Type.Schema.Schema.Type }}
{{- end }}
@@ -151,11 +165,22 @@ export const createAPIClient = (
}
{{- else if eq .Type.Kind "object" }}
if (body["{{ .Name }}"] !== undefined) {
formData.append(
if (isReactNative) {
formData.append(
"{{ .Name }}",
{
string: JSON.stringify(body["{{ .Name }}"]),
type: "application/json",
name: "",
} as unknown as Blob,
);
} else {
formData.append(
"{{ .Name }}",
new Blob([JSON.stringify(body["{{ .Name }}"])], { type: "application/json" }),
"",
);
new Blob([JSON.stringify(body["{{ .Name }}"])], { type: "application/json" }),
"",
);
}
}
{{- else }}
TODO {{ .Type.Kind }} {{ .Type.Schema.Schema.Type }}