Custom Update
Build a custom Hot Updater client flow with init, checkForUpdate, updateBundle, and reload.
Overview
Hot Updater can manage updates automatically with HotUpdater.wrap, but you can
also build the client flow yourself. A custom flow is useful when you want to
check only after login, gate updates by app state, coordinate with navigation
state, or let another runtime own the root component.
The custom flow has four parts:
- Initialize Hot Updater with
HotUpdater.init. - Decide when to call
HotUpdater.checkForUpdate. - Download the bundle with
updateInfo.updateBundle()orHotUpdater.updateBundle. - Reload immediately or later.
Initialize
Use HotUpdater.init when you do not want to wrap the root component.
import { HotUpdater } from "@hot-updater/react-native";
HotUpdater.init({
baseURL: "<your-update-server-url>",
requestHeaders: {
Authorization: "Bearer <your-access-token>",
},
});HotUpdater.init has the same initialization effect as manual wrap, without
creating an unnecessary React HOC.
HotUpdater.init({
baseURL: "<your-update-server-url>",
});
export default App;This configures Hot Updater and notifies the update server that the current app launch is ready.
Check on Your Schedule
Call checkForUpdate only when your app is ready for the decision.
async function checkForAppUpdate() {
const updateInfo = await HotUpdater.checkForUpdate({
updateStrategy: "appVersion",
});
if (!updateInfo) {
return { status: "UP_TO_DATE" as const };
}
return updateInfo;
}Common places to check:
- After authentication finishes
- After the first screen becomes interactive
- When the app returns to foreground
- Behind a remote-config or staged rollout gate
- From a user-facing "Check for updates" action
Download and Apply
The return value includes an updateBundle helper with all required update
metadata pre-filled.
const updateInfo = await HotUpdater.checkForUpdate({
updateStrategy: "appVersion",
});
if (updateInfo) {
const downloaded = await updateInfo.updateBundle();
if (downloaded && updateInfo.shouldForceUpdate) {
await HotUpdater.reload();
}
}For non-force updates, you can download in the background and reload later.
if (updateInfo && !updateInfo.shouldForceUpdate) {
await updateInfo.updateBundle();
// Later, after navigation is idle or the user accepts a prompt:
await HotUpdater.reload();
}Show Update Progress
Use useHotUpdaterStore when you want to render download progress in a
component. The store is updated from Hot Updater progress events.
import { HotUpdater, useHotUpdaterStore } from "@hot-updater/react-native";
import { useEffect, useState } from "react";
import { Pressable, Text, View } from "react-native";
type AppUpdateInfo = Awaited<ReturnType<typeof HotUpdater.checkForUpdate>>;
type AppUpdateState = {
status: "checking" | "current" | "downloading" | "ready" | "updating" | "error";
updateInfo: AppUpdateInfo;
};
function SettingsUpdateRow() {
const progress = useHotUpdaterStore((state) => state.progress);
const [appVersion] = useState(() => HotUpdater.getAppVersion() ?? "1.0.0");
const [appUpdate, setAppUpdate] = useState<AppUpdateState>({
status: "checking",
updateInfo: null,
});
useEffect(() => {
let isActive = true;
async function prepareUpdate() {
try {
const updateInfo = await HotUpdater.checkForUpdate({
updateStrategy: "appVersion",
});
if (!isActive) {
return;
}
if (!updateInfo) {
setAppUpdate({ status: "current", updateInfo: null });
return;
}
setAppUpdate({ status: "downloading", updateInfo });
const didDownload = await updateInfo.updateBundle();
if (!isActive) {
return;
}
setAppUpdate({
status: didDownload ? "ready" : "error",
updateInfo: didDownload ? updateInfo : null,
});
} catch {
if (isActive) {
setAppUpdate({ status: "error", updateInfo: null });
}
}
}
void prepareUpdate();
return () => {
isActive = false;
};
}, []);
async function applyUpdate() {
if (appUpdate.status !== "ready") {
return;
}
setAppUpdate((current) => ({ ...current, status: "updating" }));
try {
await HotUpdater.reload();
} catch {
setAppUpdate((current) => ({ ...current, status: "error" }));
}
}
const statusText =
appUpdate.status === "checking"
? "checking"
: appUpdate.status === "current"
? "current"
: appUpdate.status === "downloading"
? `downloading ${Math.round(progress * 100)}%`
: appUpdate.status === "ready"
? "restart ready"
: appUpdate.status === "updating"
? "restarting"
: "check failed";
return (
<View>
<View>
<Text>Version {appVersion}</Text>
<Text>{statusText}</Text>
</View>
{appUpdate.status === "downloading" ||
appUpdate.status === "ready" ||
appUpdate.status === "updating" ? (
<Pressable
accessibilityRole="button"
accessibilityLabel={
appUpdate.status === "ready" ? "Restart app" : "Preparing app update"
}
disabled={appUpdate.status !== "ready"}
onPress={() => void applyUpdate()}
>
<Text>{appUpdate.status === "updating" ? "Restarting" : "Restart"}</Text>
</Pressable>
) : null}
{appUpdate.status === "ready" && appUpdate.updateInfo?.message ? (
<Text>{appUpdate.updateInfo.message}</Text>
) : null}
</View>
);
}Use addListener when you need to react to the raw event directly.
import { useEffect, useState } from "react";
import { HotUpdater } from "@hot-updater/react-native";
import { Text } from "react-native";
function UpdateProgressLabel() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const unsubscribe = HotUpdater.addListener("onProgress", (event) => {
setProgress(event.progress);
});
return unsubscribe;
}, []);
return <Text>{Math.round(progress * 100)}%</Text>;
}Runtime Channel Checks
Custom flows can switch channels at check time. The switch is persisted only after the returned update is applied.
const updateInfo = await HotUpdater.checkForUpdate({
channel: "beta",
updateStrategy: "appVersion",
});
if (updateInfo) {
await updateInfo.updateBundle();
await HotUpdater.reload();
}To return to the build-time channel, call HotUpdater.resetChannel().
const reset = await HotUpdater.resetChannel();
if (reset) {
await HotUpdater.reload();
}Error Handling
Wrap your custom flow in one boundary and keep the app usable when an update check fails.
async function runUpdateFlow() {
try {
const updateInfo = await HotUpdater.checkForUpdate({
updateStrategy: "appVersion",
});
if (!updateInfo) {
return;
}
const downloaded = await updateInfo.updateBundle();
if (downloaded && updateInfo.shouldForceUpdate) {
await HotUpdater.reload();
}
} catch (error) {
reportUpdateError(error);
}
}Checklist
- Call
HotUpdater.initbefore update APIs. - Keep
notifyAppReadywired throughbaseURL. - Choose the check timing based on your app state, not module import order.
- Download with
updateInfo.updateBundle()unless you need direct control. - Use
useHotUpdaterStoreoraddListenerto show update progress. - Use
reload()immediately only for force updates or explicit user consent. - Add error reporting around the full flow.