1import { useEffect, useState } from 'react';
2
3import { getNativeStateMachineContextAsync } from './Updates';
4import { addUpdatesStateChangeListener } from './UpdatesEmitter';
5import type { UseUpdatesReturnType } from './UseUpdates.types';
6import {
7  currentlyRunning,
8  defaultUseUpdatesState,
9  reduceUpdatesStateFromContext,
10} from './UseUpdatesUtils';
11import type { UseUpdatesStateType } from './UseUpdatesUtils';
12
13/**
14 * Hook that obtains information on available updates and on the currently running update.
15 *
16 * @return the structures with information on currently running and available updates.
17 *
18 * @example
19 * ```tsx UpdatesDemo.tsx
20 * import { StatusBar } from 'expo-status-bar';
21 * import * as Updates from 'expo-updates';
22 * import React from 'react';
23 * import { Pressable, Text, View } from 'react-native';
24 *
25 * export default function UpdatesDemo() {
26 *   const {
27 *     currentlyRunning,
28 *     availableUpdate,
29 *     isUpdateAvailable,
30 *     isUpdatePending
31 *   } = Updates.useUpdates();
32 *
33 *   React.useEffect(() => {
34 *     if (isUpdatePending) {
35 *       // Update has successfully downloaded
36 *       runUpdate();
37 *     }
38 *   }, [isUpdatePending]);
39 *
40 *   // If true, we show the button to download and run the update
41 *   const showDownloadButton = isUpdateAvailable;
42 *
43 *   // Show whether or not we are running embedded code or an update
44 *   const runTypeMessage = currentlyRunning.isEmbeddedLaunch
45 *     ? 'This app is running from built-in code'
46 *     : 'This app is running an update';
47 *
48 *   return (
49 *     <View style={styles.container}>
50 *       <Text style={styles.headerText}>Updates Demo</Text>
51 *       <Text>{runTypeMessage}</Text>
52 *       <Button pressHandler={() => Updates.checkForUpdateAsync()} text="Check manually for updates" />
53 *       {showDownloadButton ? (
54 *         <Button pressHandler={() => Updates.fetchUpdateAsync()} text="Download and run update" />
55 *       ) : null}
56 *       <StatusBar style="auto" />
57 *     </View>
58 *   );
59 * }
60 * ```
61 */
62export const useUpdates: () => UseUpdatesReturnType = () => {
63  const [updatesState, setUpdatesState] = useState<UseUpdatesStateType>(defaultUseUpdatesState);
64
65  // Change the state based on native state machine context changes
66  useEffect(() => {
67    getNativeStateMachineContextAsync()
68      .then((context) => {
69        setUpdatesState((updatesState) => reduceUpdatesStateFromContext(updatesState, context));
70      })
71      .catch((error) => {
72        // Native call can fail (e.g. if in development mode), so catch the promise rejection and surface the error
73        setUpdatesState((updatesState) => ({ ...updatesState, initializationError: error }));
74      });
75    const subscription = addUpdatesStateChangeListener((event) => {
76      setUpdatesState((updatesState) => reduceUpdatesStateFromContext(updatesState, event.context));
77    });
78    return () => subscription.remove();
79  }, []);
80
81  // Return the updates info and the user facing functions
82  return {
83    currentlyRunning,
84    ...updatesState,
85  };
86};
87