or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-config.mdapp-state-config.mdindex.mdlogging-debugging.mdreact-integration.mdupdate-management.md
tile.json

react-integration.mddocs/

React Integration

React hooks and utilities for declarative update management with real-time state tracking, automatic UI updates, and seamless integration with component lifecycle.

Capabilities

useUpdates Hook

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>;
}

Advanced Hook Patterns

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;
}

Error Handling Patterns

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>
  );
}