Fetches and manages remotely-hosted assets and updates to your app's JS bundle.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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>
);
}