# useBundleDrop — Bundle Drop Docs

> Use the useBundleDrop React hook to read update status, check and stage compatible OTA updates, and build custom apply controls.

Canonical: https://bundledrop.app/docs/use-bundle-drop

## 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](/docs/init-update-manager) 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](/docs/init-update-manager) and can be changed later with `setChannel(...)`.

## What It Returns

- `status`: latest human-readable status message
- `isEnabled`: whether Bundle Drop is active for the current runtime
- `isBusy`: whether an update action is currently running
- `channelName`: active runtime channel used by OTA actions
- `setChannel(channelName)`: changes the active runtime channel
- `installedInfo`: metadata for the installed bundle, if present
- `pendingApply`: whether a downloaded update or staged rollback is waiting to be applied
- `hasBundle`: whether a downloaded bundle exists locally
- `checkLatest()`: checks for update availability
- `downloadUpdate()`: downloads and stages an update
- `applyUpdate()`: applies a staged update
- `reportHealthy()`: marks the current OTA candidate healthy in manual health mode
- `fetchAvailableChannels()`: fetches public channel names for the project
- `availableChannels`: last fetched channel list
- `fetchBundles(options?)`: fetches a paginated list of downloadable bundles for a channel
- `installBundle(bundleListItem)`: downloads and stages a specific bundle from the list

## Action Return Shapes

`checkLatest()` returns:

The response includes the decision fields below:

```ts
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:

```ts
Promise<{
  result:
    | { status: "staged"; bundlePath: string; hash: string }
    | {
        status: "upToDate";
        reason?: string;
        skippedFailedBundle?: boolean;
        skippedHash?: string;
      }
    | { status: "disabled" }
    | { status: "incompatible" }
    | { status: "rollback"; reason?: string; pendingApply?: boolean };
  status?: string;
}>
```

If `result.skippedFailedBundle` is `true`, Bundle Drop skipped the download because that bundle previously failed on this device.

A rollback result with `pendingApply: true` is waiting for your app to call `applyUpdate()` or for the next cold launch under the selected policy. See [Rollback](/docs/rollback) for policy behavior and safety exceptions.

Bundle Drop automatically fetches the smallest safe update — a binary patch when one exists, falling back to the full bundle. Your app does not need to branch on delivery details; `downloadUpdate()` only reports `staged` after Bundle Drop has reconstructed and validated the complete bundle for local use.

`applyUpdate()` returns:

```ts
Promise<{
  result:
    | { status: "applied" }
    | { status: "noBundle" }
    | { status: "disabled" }
    | { status: "alreadyApplied" }
    | { status: "blocked"; reason: "ROLLBACK_REVALIDATION_FAILED" }
    | {
        status: "blocked";
        reason: "BUNDLE_PREVIOUSLY_FAILED";
        skippedHash?: string;
      };
  status?: string;
}>
```

If `result.status` is `"blocked"`, inspect `result.reason`:

- `BUNDLE_PREVIOUSLY_FAILED`: the bundle previously failed on this device. Keep the current bundle or publish a new update; do not retry the failed bundle.
- `ROLLBACK_REVALIDATION_FAILED`: the staged rollback could not be confirmed against the current channel decision. Keep the running bundle and check for updates again before offering another apply action.

`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. Native code validates the hash and attempt identity captured by this React runtime, so a stale callback cannot mark a newer launch healthy.

`fetchBundles(options?)` returns:

```ts
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:

```ts
Promise<{
  result:
    | { status: "staged"; bundlePath: string; hash: string }
    | {
        status: "upToDate";
        reason?: string;
        skippedFailedBundle?: boolean;
        skippedHash?: string;
      }
    | { status: "disabled" }
    | { status: "incompatible" }
    | { status: "rollback"; reason?: string; pendingApply?: boolean };
  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

Set the initial channel in [BundleDrop.init](/docs/init-update-manager), for example `channelName: "develop"`. Do not switch it in a mount effect: initialization may already be running an update flow. Disable later channel changes and update actions while `isBusy` is `true`.

**UpdatesScreen.tsx**

```tsx
import { Alert, Button, Text, View } from "react-native";
import { useBundleDrop } from "@gfean/react-native-bundle-drop";

export function UpdatesScreen() {
  const {
    status, channelName, setChannel, isBusy, pendingApply,
    downloadUpdate, applyUpdate,
  } = useBundleDrop();

  async function downloadOrApply() {
    try {
      if (pendingApply) {
        await applyUpdate();
      } else {
        await downloadUpdate();
      }
    } catch (error) {
      Alert.alert("Update unavailable", String(error));
    }
  }

  function switchToBeta() {
    try {
      setChannel("Beta"); // Use a channel that exists in your project.
    } catch (error) {
      Alert.alert("Channel unchanged", String(error));
    }
  }

  return (
    <View>
      <Text>{channelName}: {status}</Text>

    </View>
  );
}
```

The SDK reports non-error outcomes, including blocked applications, through the return shapes above and `status`. A download stages the update; automatic application still follows your configured policy.

## Bundle Browsing Usage

**VersionPickerScreen.tsx**

```tsx
import { useState } from "react";
import { Alert, Button, Text, View } from "react-native";
import { useBundleDrop, type BundleListItem } from "@gfean/react-native-bundle-drop";

export function VersionPickerScreen() {
  const { isBusy, status, fetchBundles, installBundle, pendingApply, applyUpdate } = useBundleDrop();
  const [items, setItems] = useState<BundleListItem[]>([]);
  const [nextCursor, setNextCursor] = useState<string | null>(null);

  async function loadBundles(cursor?: string) {
    try {
      const page = await fetchBundles({ limit: 10, cursor });
      setItems((current) => cursor ? [...current, ...page.items] : page.items);
      setNextCursor(page.nextCursor);
    } catch (error) {
      Alert.alert("Couldn't load updates", String(error));
    }
  }

  async function stageBundle(item: BundleListItem) {
    try {
      await installBundle(item);
    } catch (error) {
      Alert.alert("Couldn't download update", String(error));
    }
  }

  async function applyPendingUpdate() {
    try {
      await applyUpdate();
    } catch (error) {
      Alert.alert("Couldn't apply update", String(error));
    }
  }

  return (
    <View>
      <Text>{status}</Text>
      <Button title="Load updates" disabled={isBusy} onPress={() => loadBundles()} />
      {items.map((item) => (
        <Button key={item.hash} title={"Bundle v" + item.bundleVersion}
          disabled={isBusy} onPress={() => stageBundle(item)} />
      ))}
      {nextCursor && (
        <Button title="Load more" disabled={isBusy} onPress={() => loadBundles(nextCursor)} />
      )}

    </View>
  );
}
```

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](/docs/init-update-manager) is the required startup controller
- `useBundleDrop` is 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](/docs/runtime-apis).
- For runtime startup setup, see [BundleDrop.init](/docs/init-update-manager).
- For policy behavior, see [Update Policies](/docs/update-policies).
- For SDK overview, see [SDK Integration](/docs/sdk-integration).
