skip to content

With expo-updates, how would you let a news app download an update mid-session and prompt readers to restart into it?

level: middleimportance: must knowfreq 48%

answer

  1. check, then fetch, then reload
  2. checkForUpdateAsync returns isAvailable
  3. fetchUpdateAsync stores it for next launch
  4. useUpdates exposes isUpdatePending
  5. reloadAsync rejects in development

basics

~20 s

Call Updates.checkForUpdateAsync() at a sensible moment, then fetchUpdateAsync() if isAvailable is true; when useUpdates() reports isUpdatePending, show a restart prompt whose button calls Updates.reloadAsync(). If the reader declines, the update runs on the next cold start.

solid answer

~40 s

I would keep the default non-blocking launch and add an in-session path. At launch, or when the app returns to the foreground, call `Updates.checkForUpdateAsync()`; if `isAvailable` is `true`, call `Updates.fetchUpdateAsync()`, which downloads the manifest and assets without applying them. A component using the `useUpdates()` hook re-renders when `isUpdatePending` becomes `true` (a downloaded update is waiting, whether my code or the startup check fetched it) and shows a banner such as "New version ready, tap to restart". The button calls `Updates.reloadAsync()`, which restarts the JavaScript runtime on the new bundle. If the reader ignores the banner, the update runs on the next cold start anyway. All three calls reject in development and when expo-updates is disabled, so I guard them and test in a release build.

code

tsx · 36 lines
tsx
import { useEffect } from 'react';
import { Pressable, Text } from 'react-native';
import * as Updates from 'expo-updates';

async function checkAndDownload(): Promise<void> {
  if (__DEV__ || !Updates.isEnabled) return;
  try {
    const check = await Updates.checkForUpdateAsync();
    if (check.isAvailable) {
      await Updates.fetchUpdateAsync();
    }
  } catch {
    // Offline or server error: keep reading on the bundle already running.
  }
}

export function RestartBanner() {
  const { isUpdatePending } = Updates.useUpdates();

  useEffect(() => {
    void checkAndDownload();
  }, []);

  if (!isUpdatePending) return null;

  return (
    <Pressable
      accessibilityRole="button"
      onPress={() => {
        Updates.reloadAsync().catch(() => {});
      }}
    >
      <Text>A new version of the reader is ready. Tap to restart.</Text>
    </Pressable>
  );
}

go deeper

for a junior

Name the three calls in order, check, fetch, reload, and say that each one rejects in development builds served by npx expo start.

for a middle

Explain what each call returns (isAvailable, isNew), how useUpdates exposes isUpdatePending, and why a declined update still runs at the next cold start.

for a senior

Handle the failure paths: offline checks, a disabled module, no logic after reloadAsync, and choosing prompt versus automatic reload by what the user would lose.

for a principal

Decide when a forced restart is justified and who owns that call, knowing expo-updates offers no built-in mandatory-update mechanism.

## The three-step client API **expo-updates** gives app code three async functions that together apply an **EAS Update** without waiting for the next cold launch: 1. **`checkForUpdateAsync()`** asks the update server whether a newer update exists for this build's runtime version and channel. It downloads nothing. It resolves to an `UpdateCheckResult` with `isAvailable`, a `manifest` when one is available, `isRollBackToEmbedded` for a rollback directive, and a `reason` when nothing is available (for example `noUpdateAvailableOnServer` or `updatePreviouslyFailed`). 2. **`fetchUpdateAsync()`** downloads the most recent update into local storage and resolves to an `UpdateFetchResult` with `isNew` and the `manifest`. The update is now stored; if nothing else happens it launches on the next cold start. 3. **`reloadAsync()`** restarts the JavaScript runtime on the most recently downloaded update. It is not the same as `reloadAppAsync()` from the `expo` package, which reloads without switching bundles. The docs warn not to place meaningful logic after `await Updates.reloadAsync()`: the promise resolves just before the reload is posted, and whether any more JavaScript runs is not guaranteed. ## Reacting with the useUpdates hook `useUpdates()` subscribes a component to expo-updates' native state machine, so it re-renders on every transition. The fields that matter for a restart prompt: | Field | Meaning | |---|---| | `isUpdateAvailable` | A newer update was found by the startup check or `checkForUpdateAsync()` | | `isDownloading` / `downloadProgress` | A download is running; progress goes from 0 to 1 | | `isUpdatePending` | An update has been downloaded and is waiting to run | | `checkError` / `downloadError` | The last check or download failed | | `currentlyRunning` | `updateId`, `channel`, `isEmbeddedLaunch` and more for the running bundle | Because the state comes from the native side, a banner driven by `isUpdatePending` appears whether your code fetched the update or the default launch-time check did. ## A restart prompt for a news reader A news app is a good fit for prompting rather than forcing: a reader halfway through an article should not lose their place to a surprise reload. A sensible flow: - check shortly after launch and when the app comes back to the foreground (detecting the foreground transition is an `AppState` concern); - download silently when an update is available; - show a dismissible banner once `isUpdatePending` is `true`; - call `reloadAsync()` only when the reader taps it; otherwise the next cold start applies it. The Expo docs advise against polling in a tight loop: each check costs bandwidth and battery, and requests to the hosted service may be rate limited. Checking on launch and on foreground is the usual rhythm. ## Guards and failure paths - In development (`__DEV__`, or a project served by `npx expo start`) all three functions reject with `ERR_UPDATES_DISABLED`; test in a release build (`npx expo run:android --variant release`). - When `Updates.isEnabled` is `false` (disabled in config, a missing URL or runtime version, a storage failure at start-up) they also reject, and the embedded bundle runs. - A network failure rejects `checkForUpdateAsync()` or `fetchUpdateAsync()`; catch it and keep running the current bundle. - `reloadAsync({ reloadScreenOptions })` can show a custom reload screen during the swap, which helps the restart feel intentional. ## Showing progress and errors For a large update (new images, fonts, a bigger bundle) the banner can show a progress bar while `isDownloading` is `true`, reading `downloadProgress`, which moves more smoothly when the server sends a `Content-Length` header for the assets. If `checkError` or `downloadError` is set, the sensible response in a reader app is to say nothing: the current bundle keeps working and the next check will try again. Surfacing a network error for an optional update only teaches readers to ignore the banner. `restartCount` on the hook counts JavaScript restarts since the cold start, which helps confirm in diagnostics that a reload actually happened. ## Why not reload automatically? Calling `reloadAsync()` as soon as `isUpdatePending` flips (the pattern in the hook's own doc example) is fine for a kiosk or an app with no in-progress state. For a reader app it throws away scroll position, an open article and anything typed, so a prompt is the kinder default. expo-updates has no built-in notion of a mandatory update; if one release must be forced, the app has to implement that decision itself.

  • What happens if the reader never taps the restart banner?
    Nothing is lost. `fetchUpdateAsync()` has already stored the update, and expo-updates launches the newest stored update on the next cold start, so the reader gets it the next time the app is started from a killed state. The banner only speeds that up for readers who choose to restart now.
  • How is Updates.reloadAsync() different from reloadAppAsync() in the expo package?
    `Updates.reloadAsync()` reloads onto the most recently downloaded update, switching the JavaScript bundle. `reloadAppAsync()` from `expo` restarts the JavaScript runtime but keeps the bundle it was running, which is useful for resetting state but does not apply an update.

saying these in an interview costs you the question

  • Expects checkForUpdateAsync to download the update as well
  • Thinks a declined update is discarded and must be fetched again
  • Tests the flow in npx expo start and concludes the API is broken
  • Puts navigation or saving logic after await Updates.reloadAsync()
  • Polls checkForUpdateAsync every few seconds while the app is open