Skip to content
Files SDK
Esc
navigateopen⌘Jpreview
On this page

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 a ReadableStream polyfill (Hermes has none) — prefer blob() / arrayBuffer().
  • StoredFile.text() relies on TextDecoder, present on recent Hermes; polyfill it on older runtimes.
  • Presigned POST upload targets (S3-family adapters with a maxUploadSize gateway cap) require a NativeFileRef body — React Native’s FormData cannot 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.

Was this page helpful?