useBundleDrop
What useBundleDrop Is
useBundleDrop is the React hook for interacting with the initialized Bundle Drop runtime.
It gives you state plus explicit actions so you can build your own update UI, such as a "Check for updates" button, a download button, or a custom update prompt.
When to Use It
Use useBundleDrop when you want the app to decide how and when update actions happen from React UI.
This is the better fit when:
- users trigger update checks manually
- you want custom buttons, prompts, or settings screens
- you want to switch channels from your own UI
Call BundleDrop.init first, then use the hook inside your screens or components.
Hook Options
useBundleDrop() takes no arguments.
The active channel comes from the runtime started by BundleDrop.init and can be changed later with setChannel(...).
What It Returns
status: latest human-readable status messageisEnabled: whether Bundle Drop is active for the current runtimeisBusy: whether an update action is currently runningchannelName: active runtime channel used by OTA actionssetChannel(channelName): changes the active runtime channelinstalledInfo: metadata for the installed bundle, if presentpendingApply: whether a downloaded bundle is waiting to be appliedhasBundle: whether a downloaded bundle exists locallycheckLatest(): checks for update availabilitydownloadUpdate(): downloads and stages an updateapplyUpdate(): applies a staged updatereportHealthy(): marks the current OTA candidate healthy in manual health modefetchAvailableChannels(): fetches public channel names for the projectavailableChannels: last fetched channel listfetchBundles(options?): fetches a paginated list of downloadable bundles for a channelinstallBundle(bundleListItem): downloads and stages a specific bundle from the list
Action Return Shapes
checkLatest() returns:
The response includes the decision fields below:
Promise<{
response:
| {
action: "NOOP" | "INSTALL" | "ROLLBACK";
upToDate?: boolean;
bundleVersion?: number;
version?: string;
hash?: string;
channelName?: string;
reason?: string;
skippedFailedBundle?: boolean;
skippedHash?: string;
incompatible?: boolean;
runtimeVersion?: string;
requestedRuntimeVersion?: string;
latestRuntimeVersionOnChannel?: string;
}
| null;
status?: string;
}>
If response.skippedFailedBundle is true, Bundle Drop kept the current bundle because the selected update previously failed on this device.
When response.action is "INSTALL", your UI usually only needs to show that an update is available. The response may also include SDK-managed delivery metadata used by downloadUpdate(); treat those fields as diagnostic-only unless another API explicitly asks for them.
downloadUpdate() returns:
Promise<{
result:
| { status: "staged"; bundlePath: string; hash: string }
| {
status: "upToDate";
reason?: string;
skippedFailedBundle?: boolean;
skippedHash?: string;
}
| { status: "disabled" }
| { status: "incompatible" }
| { status: "rollback"; reason?: string };
status?: string;
}>
If result.skippedFailedBundle is true, Bundle Drop skipped the download because that bundle previously failed on this device.
Bundle Drop may optimize the download automatically. Your app does not need to branch on delivery details; downloadUpdate() only stages an update after Bundle Drop has validated it for local use.
applyUpdate() returns:
Promise<{
result:
| { status: "applied" }
| { status: "noBundle" }
| { status: "disabled" }
| { status: "alreadyApplied" }
| {
status: "blocked";
reason: "BUNDLE_PREVIOUSLY_FAILED";
skippedHash?: string;
};
status?: string;
}>
If result.status is "blocked", the staged bundle previously failed on this device and was not applied. Do not retry that same update on the same device; publish a new bundle or keep the current bundle.
reportHealthy() returns:
Promise<void>
Use it in manual health mode after your app has completed the startup work required before keeping the current OTA bundle.
fetchBundles(options?) returns:
Promise<{
items: BundleListItem[];
nextCursor: string | null;
hasMore: boolean;
}>
Each BundleListItem contains hash, bundleVersion, version, platform, runtimeVersion, releaseNotes, createdAt, and downloadUrl.
hash is the stable Bundle Drop identifier for that update. Use it for display, support, analytics, or error-reporting correlation. Most apps should not try to interpret it.
Options: channelName, platform, limit (1–50), cursor.
installBundle(bundleListItem) returns:
Promise<{
result:
| { status: "staged"; bundlePath: string; hash: string }
| {
status: "upToDate";
reason?: string;
skippedFailedBundle?: boolean;
skippedHash?: string;
}
| { status: "disabled" }
| { status: "incompatible" }
| { status: "rollback"; reason?: string };
status?: string;
}>
If result.skippedFailedBundle is true, Bundle Drop kept that update out of this device. Treat it as no update for the current device, not as a retryable install error.
Typical Usage
const {
status,
channelName,
setChannel,
isBusy,
installedInfo,
pendingApply,
checkLatest,
downloadUpdate,
applyUpdate,
} = useBundleDrop();
useEffect(() => {
setChannel("Beta");
}, [setChannel]);
// Example flow:
// 1. User taps "Check for updates"
// 2. User taps "Download"
// 3. User taps "Apply now" or waits for next launchBundle Browsing Usage
const {
channelName,
setChannel,
isBusy,
status,
fetchBundles,
installBundle,
applyUpdate,
} = useBundleDrop();
useEffect(() => {
setChannel("Beta");
}, [setChannel]);
// Fetch available bundles
const page = await fetchBundles({ limit: 10 });
// page.items = [{ hash, bundleVersion, version, releaseNotes, ... }]
// Load more
const nextPage = await fetchBundles({ cursor: page.nextCursor });
// User picks a version from the list
const result = await installBundle(page.items[2]);
if (result.result.status === "staged") {
await applyUpdate(); // reload with the selected version
}Bundle Drop handles the download and staging. Your app renders the list, handles selection, and calls applyUpdate() when it is ready to switch.
How It Differs from BundleDrop.init
The difference is mostly about control:
- BundleDrop.init is the required startup controller
useBundleDropis the React helper for manual, UI-driven behavior on top of that runtime
You can still use the same underlying Bundle Drop integration either way. The difference is whether the app handles updates automatically at startup or exposes that flow through your own interface.
Best Fit
useBundleDrop is best for:
- custom update screens
- settings-based update controls
- apps that want explicit user interaction around updates
Related Docs
- For lower-level functions and their raw return shapes, see Runtime APIs.
- For runtime startup setup, see BundleDrop.init.
- For policy behavior, see Update Policies.
- For SDK overview, see SDK Integration.
