React hooks and utilities for declarative update management with real-time state tracking, automatic UI updates, and seamless integration with component lifecycle.
Primary React hook that provides comprehensive update state information and real-time updates via the native state machine.
/**
* Hook that obtains information on available updates and on the currently running update.
* Automatically subscribes to native state changes and updates component state accordingly.
* @returns Object with information on currently running and available updates
*/
function useUpdates(): UseUpdatesReturnType;
interface UseUpdatesReturnType {
/** Information on the currently running app */
currentlyRunning: CurrentlyRunningInfo;
/** Whether the startup procedure is still running */
isStartupProcedureRunning: boolean;
/** Information about available update, if found */
availableUpdate?: UpdateInfo;
/** Information about downloaded update, if any */
downloadedUpdate?: UpdateInfo;
/** True if a new available update has been found */
isUpdateAvailable: boolean;
/** True if a new available update is available and has been downloaded */
isUpdatePending: boolean;
/** True if the app is currently checking for a new available update from the server */
isChecking: boolean;
/** True if the app is currently downloading an update from the server */
isDownloading: boolean;
/** True if the app is currently in the process of restarting */
isRestarting: boolean;
/** Number of times the JS has been restarted since app cold start */
restartCount: number;
/** Error from startup check or checkForUpdateAsync call */
checkError?: Error;
/** Error from startup download or fetchUpdateAsync call */
downloadError?: Error;
/** Last time this client checked for an available update since app started */
lastCheckForUpdateTimeSinceRestart?: Date;
/** Download progress from 0 to 1 if isDownloading is true */
downloadProgress?: number;
}
interface CurrentlyRunningInfo {
/** The UUID that uniquely identifies the currently running update */
updateId?: string;
/** The channel name of the current build, if configured for use with EAS Update */
channel?: string;
/** Creation time of the update that's currently running */
createdAt?: Date;
/** True if currently running update is the one embedded in the build */
isEmbeddedLaunch: boolean;
/** True if app is launching under emergency fallback mechanism */
isEmergencyLaunch: boolean;
/** Error message if isEmergencyLaunch is true */
emergencyLaunchReason: string | null;
/** Number of milliseconds it took to launch */
launchDuration?: number;
/** Manifest object for the update that's currently running */
manifest?: Partial<Manifest>;
/** Runtime version of the current build */
runtimeVersion?: string;
}
type UpdateInfo = UpdateInfoNew | UpdateInfoRollback;
interface UpdateInfoNew {
/** The type of update */
type: UpdateInfoType.NEW;
/** String that uniquely identifies the update */
updateId: string;
/** Creation time or commit time of the update */
createdAt: Date;
/** Manifest for the update */
manifest: Manifest;
}
interface UpdateInfoRollback {
/** The type of update */
type: UpdateInfoType.ROLLBACK;
/** Always undefined for rollback updates */
updateId: undefined;
/** Creation time or commit time of the update */
createdAt: Date;
/** Always undefined for rollback updates */
manifest: undefined;
}
enum UpdateInfoType {
/** New updates found on or downloaded from the update server */
NEW = 'new',
/** Update is a directive to roll back to the embedded bundle */
ROLLBACK = 'rollback'
}Usage Examples:
import React, { useEffect } from 'react';
import { View, Text, Button, ProgressBar } from 'react-native';
import { useUpdates } from 'expo-updates';
import * as Updates from 'expo-updates';
// Basic usage - automatic update handling
function AutoUpdateComponent() {
const {
currentlyRunning,
isUpdateAvailable,
isUpdatePending,
isDownloading,
downloadProgress
} = useUpdates();
useEffect(() => {
if (isUpdatePending) {
// Update has successfully downloaded; apply it now
Updates.reloadAsync();
}
}, [isUpdatePending]);
// Show download button if update is available
const showDownloadButton = isUpdateAvailable && !isDownloading;
// Show whether running embedded code or an update
const runTypeMessage = currentlyRunning.isEmbeddedLaunch
? 'This app is running from built-in code'
: 'This app is running an update';
return (
<View>
<Text>{runTypeMessage}</Text>
{showDownloadButton && (
<Button
onPress={() => Updates.fetchUpdateAsync()}
title="Download and run update"
/>
)}
{isDownloading && (
<View>
<Text>Downloading update...</Text>
<ProgressBar progress={downloadProgress || 0} />
</View>
)}
</View>
);
}
// Advanced usage - manual update management
function ManualUpdateComponent() {
const {
currentlyRunning,
availableUpdate,
downloadedUpdate,
isChecking,
isDownloading,
isRestarting,
checkError,
downloadError,
downloadProgress,
lastCheckForUpdateTimeSinceRestart
} = useUpdates();
const handleManualCheck = async () => {
try {
await Updates.checkForUpdateAsync();
} catch (error) {
console.error('Manual check failed:', error);
}
};
const handleDownload = async () => {
if (availableUpdate) {
try {
await Updates.fetchUpdateAsync();
} catch (error) {
console.error('Download failed:', error);
}
}
};
const handleRestart = async () => {
if (downloadedUpdate) {
try {
await Updates.reloadAsync();
} catch (error) {
console.error('Restart failed:', error);
}
}
};
return (
<View>
<Text>Current Update: {currentlyRunning.updateId || 'Embedded'}</Text>
<Text>Channel: {currentlyRunning.channel || 'None'}</Text>
{lastCheckForUpdateTimeSinceRestart && (
<Text>
Last Check: {lastCheckForUpdateTimeSinceRestart.toLocaleTimeString()}
</Text>
)}
<Button
title="Check for Updates"
onPress={handleManualCheck}
disabled={isChecking}
/>
{isChecking && <Text>Checking for updates...</Text>}
{checkError && (
<Text style={{color: 'red'}}>
Check Error: {checkError.message}
</Text>
)}
{availableUpdate && (
<View>
<Text>Update Available: {availableUpdate.updateId}</Text>
<Text>Created: {availableUpdate.createdAt.toLocaleDateString()}</Text>
<Button
title="Download Update"
onPress={handleDownload}
disabled={isDownloading}
/>
</View>
)}
{isDownloading && (
<View>
<Text>Downloading... {Math.round((downloadProgress || 0) * 100)}%</Text>
<ProgressBar progress={downloadProgress || 0} />
</View>
)}
{downloadError && (
<Text style={{color: 'red'}}>
Download Error: {downloadError.message}
</Text>
)}
{downloadedUpdate && (
<View>
<Text>Update Ready: {downloadedUpdate.updateId}</Text>
<Button
title="Restart App"
onPress={handleRestart}
disabled={isRestarting}
/>
</View>
)}
{isRestarting && <Text>Restarting app...</Text>}
</View>
);
}
// Update status indicator component
function UpdateStatusIndicator() {
const {
isUpdateAvailable,
isUpdatePending,
isDownloading,
downloadProgress
} = useUpdates();
if (isUpdatePending) {
return <Text style={{color: 'green'}}>Update ready - restarting...</Text>;
}
if (isDownloading) {
return (
<Text style={{color: 'blue'}}>
Downloading {Math.round((downloadProgress || 0) * 100)}%
</Text>
);
}
if (isUpdateAvailable) {
return <Text style={{color: 'orange'}}>Update available</Text>;
}
return <Text style={{color: 'gray'}}>Up to date</Text>;
}Common patterns for integrating the useUpdates hook with app state management and lifecycle.
// Integration with app state management
import { useUpdates } from 'expo-updates';
import { useAppState } from './app-state-context';
function useUpdateWithAppState() {
const updates = useUpdates();
const { setUpdateStatus } = useAppState();
useEffect(() => {
setUpdateStatus({
hasUpdate: updates.isUpdateAvailable,
isDownloading: updates.isDownloading,
progress: updates.downloadProgress,
error: updates.checkError || updates.downloadError
});
}, [
updates.isUpdateAvailable,
updates.isDownloading,
updates.downloadProgress,
updates.checkError,
updates.downloadError
]);
return updates;
}
// Background update checking
function useBackgroundUpdateCheck() {
const updates = useUpdates();
useEffect(() => {
const checkForUpdates = async () => {
if (!updates.isChecking && !updates.isDownloading) {
try {
await Updates.checkForUpdateAsync();
} catch (error) {
console.log('Background check failed:', error);
}
}
};
// Check for updates when app becomes active
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'active') {
checkForUpdates();
}
});
return () => subscription?.remove();
}, [updates.isChecking, updates.isDownloading]);
return updates;
}
// Update notification system
function useUpdateNotifications() {
const updates = useUpdates();
const [lastNotificationId, setLastNotificationId] = useState<string | null>(null);
useEffect(() => {
if (updates.availableUpdate &&
updates.availableUpdate.updateId !== lastNotificationId) {
// Show notification about available update
showNotification({
title: 'Update Available',
message: 'A new version of the app is available',
actions: [
{ text: 'Download', action: () => Updates.fetchUpdateAsync() },
{ text: 'Later', action: () => {} }
]
});
setLastNotificationId(updates.availableUpdate.updateId);
}
}, [updates.availableUpdate, lastNotificationId]);
useEffect(() => {
if (updates.isUpdatePending) {
showNotification({
title: 'Update Ready',
message: 'Restart the app to apply the update',
actions: [
{ text: 'Restart', action: () => Updates.reloadAsync() },
{ text: 'Later', action: () => {} }
]
});
}
}, [updates.isUpdatePending]);
return updates;
}Best practices for handling errors in React components using the useUpdates hook.
function UpdatesWithErrorHandling() {
const updates = useUpdates();
const [userError, setUserError] = useState<string | null>(null);
// Handle check errors
useEffect(() => {
if (updates.checkError) {
const message = updates.checkError.code === 'ERR_UPDATES_DISABLED'
? 'Updates are disabled in development mode'
: `Failed to check for updates: ${updates.checkError.message}`;
setUserError(message);
// Clear error after showing it
setTimeout(() => setUserError(null), 5000);
}
}, [updates.checkError]);
// Handle download errors
useEffect(() => {
if (updates.downloadError) {
setUserError(`Failed to download update: ${updates.downloadError.message}`);
setTimeout(() => setUserError(null), 5000);
}
}, [updates.downloadError]);
const handleRetryCheck = async () => {
setUserError(null);
try {
await Updates.checkForUpdateAsync();
} catch (error) {
setUserError(`Retry failed: ${error.message}`);
}
};
const handleRetryDownload = async () => {
setUserError(null);
try {
await Updates.fetchUpdateAsync();
} catch (error) {
setUserError(`Download retry failed: ${error.message}`);
}
};
return (
<View>
{userError && (
<View style={{backgroundColor: 'red', padding: 10}}>
<Text style={{color: 'white'}}>{userError}</Text>
<Button title="Retry" onPress={
updates.checkError ? handleRetryCheck : handleRetryDownload
} />
</View>
)}
{/* Rest of component */}
</View>
);
}