1import { useEffect, useLayoutEffect, useState } from 'react';
2
3import { NotificationResponse } from './Notifications.types';
4import { addNotificationResponseReceivedListener } from './NotificationsEmitter';
5import NotificationsEmitterModule from './NotificationsEmitterModule';
6
7/**
8 * A React hook always returns the notification response that was received most recently
9 * (a notification response designates an interaction with a notification, such as tapping on it).
10 *
11 * > If you don't want to use a hook, you can use `Notifications.getLastNotificationResponseAsync()` instead.
12 *
13 * @return The hook may return one of these three types/values:
14 * - `undefined` - until we're sure of what to return,
15 * - `null` - if no notification response has been received yet,
16 * - a [`NotificationResponse`](#notificationresponse) object - if a notification response was received.
17 *
18 * @example Responding to a notification tap by opening a URL that could be put into the notification's `data`
19 * (opening the URL is your responsibility and is not a part of the `expo-notifications` API):
20 * ```jsx
21 * import * as Notifications from 'expo-notifications';
22 * import { Linking } from 'react-native';
23 *
24 * export default function App() {
25 *   const lastNotificationResponse = Notifications.useLastNotificationResponse();
26 *   React.useEffect(() => {
27 *     if (
28 *       lastNotificationResponse &&
29 *       lastNotificationResponse.notification.request.content.data.url &&
30 *       lastNotificationResponse.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER
31 *     ) {
32 *       Linking.openURL(lastNotificationResponse.notification.request.content.data.url);
33 *     }
34 *   }, [lastNotificationResponse]);
35 *   return (
36 *     // Your app content
37 *   );
38 * }
39 * ```
40 * @header listen
41 */
42export default function useLastNotificationResponse() {
43  const [lastNotificationResponse, setLastNotificationResponse] = useState<
44    NotificationResponse | null | undefined
45  >(undefined);
46
47  // useLayoutEffect ensures the listener is registered as soon as possible
48  useLayoutEffect(() => {
49    const subscription = addNotificationResponseReceivedListener((response) => {
50      setLastNotificationResponse(response);
51    });
52    return () => {
53      subscription.remove();
54    };
55  }, []);
56
57  // On each mount of this hook we fetch last notification response
58  // from the native module which is an "always active listener"
59  // and always returns the most recent response.
60  useEffect(() => {
61    NotificationsEmitterModule.getLastNotificationResponseAsync?.().then((response) => {
62      // We only update the state with the resolved value if it's empty,
63      // because if it's not empty it must have been populated by the `useLayoutEffect`
64      // listener which returns "live" values.
65      setLastNotificationResponse((currentResponse) => currentResponse ?? response);
66    });
67  }, []);
68
69  return lastNotificationResponse;
70}
71