React Native
The same useFiles hook in React Native / Expo — absolute endpoint, picker-descriptor uploads with real progress, buffered downloads.
files-sdk/react has no DOM dependency, so useFiles works in React Native and Expo as-is — same verbs, same progress and error state, talking to the same gateway your web app uses. Two platform differences matter: the endpoint must be absolute, and uploads take the picker’s { uri, name, type } descriptor directly.
const files = useFiles({
endpoint: "https://app.example.com/api/files", // React Native fetch needs an absolute URL
headers: () => ({ authorization: `Bearer ${token}` }), // your auth token
});
Uploading from a picker
Pass the picker asset straight to upload as a NativeFileRef — the { uri, name, type } shape expo-document-picker / expo-image-picker already return:
import * as DocumentPicker from "expo-document-picker";
const pick = async () => {
const result = await DocumentPicker.getDocumentAsync();
if (result.canceled) {
return;
}
const { uri, name, mimeType, size } = result.assets[0];
const { key } = await files.upload(
{ uri, name, type: mimeType, size },
{ onProgress: (p) => setProgress(p.fraction) } // 0 → 1
);
};
The client picks the right wire form per target: a presigned POST target appends the descriptor to React Native’s FormData, which streams it from disk without buffering; every other path (presigned PUT, the through-the-gateway upload) resolves the uri to a Blob first. Explicit keys work the same way — files.upload("avatars/me.png", { uri, type: mimeType }).
Progress is real: React Native ships XMLHttpRequest with upload progress events, so the default transport reports byte-level progress exactly as in the browser. Blob bodies (fetch(uri).then((r) => r.blob())) still work, and byte bodies do too — files.upload(key, bytes) detects that React Native’s Blob cannot wrap an ArrayBuffer and sends the bytes raw instead.
Downloading
React Native’s fetch has no response streaming, so download() transparently buffers the body — arrayBuffer() and text() behave exactly as on the web, and blob() hands back the response’s native Blob. One ordering caveat: React Native’s Blob cannot wrap raw bytes, so call blob() before arrayBuffer()/text() if you need one — once a byte accessor has drained the response, blob() throws:
const file = await files.download("report.pdf");
const bytes = await file.arrayBuffer();
For displaying or saving media, skip the byte round-trip and use a signed URL:
import * as FileSystem from "expo-file-system";
<Image source={{ uri: avatarUrl }} />; // from await files.url("avatar.png")
await FileSystem.downloadAsync(
await files.url("report.pdf"),
FileSystem.documentDirectory + "report.pdf"
);
Caveats
StoredFile.stream()needs aReadableStreampolyfill (Hermes has none) — preferblob()/arrayBuffer().StoredFile.text()relies onTextDecoder, present on recent Hermes; polyfill it on older runtimes.- Presigned POST upload targets (S3-family adapters with a
maxUploadSizegateway cap) require aNativeFileRefbody — React Native’sFormDatacannot carry a Blob part, but it streams a descriptor natively. Presigned PUT targets and the through-the-gateway path accept any body form. - The UI components are React DOM + Tailwind, so they are web-only; the hook state (
uploads,progress,error) maps directly onto your native components instead.