Use when implementing over-the-air (OTA) updates with Expo Updates. Covers update configuration, checking for updates, and update strategies.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
name: jutsu-expo:expo-updates description: Use when implementing over-the-air (OTA) updates with Expo Updates. Covers update configuration, checking for updates, and update strategies. allowed-tools:
Use this skill when implementing over-the-air (OTA) updates to deploy JavaScript and asset updates without app store releases.
// app.json
{
"expo": {
"updates": {
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0,
"url": "https://u.expo.dev/[project-id]"
},
"runtimeVersion": {
"policy": "sdkVersion"
}
}
}
import * as Updates from 'expo-updates';
import { useEffect, useState } from 'react';
import { View, Text, Button } from 'react-native';
export default function App() {
const [updateAvailable, setUpdateAvailable] = useState(false);
useEffect(() => {
async function checkForUpdates() {
if (!__DEV__) {
const update = await Updates.checkForUpdateAsync();
setUpdateAvailable(update.isAvailable);
}
}
checkForUpdates();
}, []);
const handleUpdate = async () => {
const { isNew } = await Updates.fetchUpdateAsync();
if (isNew) {
await Updates.reloadAsync();
}
};
if (updateAvailable) {
return (
<View>
<Text>Update Available!</Text>
<Button title="Update Now" onPress={handleUpdate} />
</View>
);
}
return <View>{/* Your app */}</View>;
}
// app.config.ts
export default {
expo: {
runtimeVersion: {
policy: 'appVersion', // Match app version
},
// Or use custom logic
runtimeVersion: '1.0.0',
},
};
import * as Updates from 'expo-updates';
import { useEffect, useState } from 'react';
export function useUpdates() {
const [isChecking, setIsChecking] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const [updateAvailable, setUpdateAvailable] = useState(false);
useEffect(() => {
const subscription = Updates.addListener((event) => {
if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) {
setUpdateAvailable(true);
}
});
return () => subscription.remove();
}, []);
const checkForUpdate = async () => {
if (__DEV__) return;
setIsChecking(true);
try {
const update = await Updates.checkForUpdateAsync();
setUpdateAvailable(update.isAvailable);
} finally {
setIsChecking(false);
}
};
const downloadAndApplyUpdate = async () => {
if (__DEV__) return;
setIsDownloading(true);
try {
const update = await Updates.fetchUpdateAsync();
if (update.isNew) {
await Updates.reloadAsync();
}
} finally {
setIsDownloading(false);
}
};
return {
isChecking,
isDownloading,
updateAvailable,
checkForUpdate,
downloadAndApplyUpdate,
};
}
import { useEffect } from 'react';
import * as Updates from 'expo-updates';
export function useSilentUpdates() {
useEffect(() => {
async function update() {
if (__DEV__) return;
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
// Don't reload immediately - wait for next app start
}
} catch (error) {
console.error('Update check failed:', error);
}
}
update();
}, []);
}
import * as Updates from 'expo-updates';
import { useState } from 'react';
import { View, Text, Button, ActivityIndicator } from 'react-native';
export function UpdateScreen() {
const [loading, setLoading] = useState(false);
const handleUpdate = async () => {
setLoading(true);
try {
const update = await Updates.fetchUpdateAsync();
if (update.isNew) {
await Updates.reloadAsync();
}
} catch (error) {
console.error('Update failed:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
<Text>Updating...</Text>
</View>
);
}
return (
<View>
<Text>Update Available</Text>
<Button title="Update Now" onPress={handleUpdate} />
</View>
);
}
// eas.json
{
"build": {
"production": {
"channel": "production"
},
"preview": {
"channel": "preview"
}
}
}