mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-06-23 06:59:10 -04:00
Merge pull request #3356 from inaturalist/mob-187-notifications-should-time-out-when-theres-low-connectivity
show offline/retry state for notifications when request takes too long
This commit is contained in:
@@ -14,6 +14,9 @@ import NotificationsTab, {
|
||||
OWNER_TAB,
|
||||
} from "./NotificationsTab";
|
||||
|
||||
const OWNER_TAB_PARAMS = { observations_by: "owner" } as const;
|
||||
const FOLLOWING_TAB_PARAMS = { observations_by: "following" } as const;
|
||||
|
||||
const Notifications = ( ) => {
|
||||
const [activeTab, setActiveTab] = useState<typeof OWNER_TAB | typeof OTHER_TAB>( OWNER_TAB );
|
||||
const { t } = useTranslation();
|
||||
@@ -44,14 +47,14 @@ const Notifications = ( ) => {
|
||||
{activeTab === OWNER_TAB && (
|
||||
<NotificationsContainer
|
||||
currentUser={currentUser}
|
||||
notificationParams={{ observations_by: "owner" }}
|
||||
notificationParams={OWNER_TAB_PARAMS}
|
||||
onRefresh={( ) => EventRegister.emit( NOTIFICATIONS_REFRESHED, OWNER_TAB )}
|
||||
/>
|
||||
)}
|
||||
{activeTab === OTHER_TAB && (
|
||||
<NotificationsContainer
|
||||
currentUser={currentUser}
|
||||
notificationParams={{ observations_by: "following" }}
|
||||
notificationParams={FOLLOWING_TAB_PARAMS}
|
||||
onRefresh={( ) => EventRegister.emit( NOTIFICATIONS_REFRESHED, OTHER_TAB )}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -32,6 +32,7 @@ const NotificationsContainer = ( {
|
||||
isError,
|
||||
isFetching,
|
||||
isInitialLoading,
|
||||
loadingTimedOut,
|
||||
notifications,
|
||||
refetch,
|
||||
} = useInfiniteNotificationsScroll( notificationParams );
|
||||
@@ -69,6 +70,7 @@ const NotificationsContainer = ( {
|
||||
isFetching={isFetching}
|
||||
isInitialLoading={isInitialLoading}
|
||||
isConnected={isConnected}
|
||||
loadingTimedOut={loadingTimedOut}
|
||||
onEndReached={fetchNextPage}
|
||||
reload={refetch}
|
||||
refreshing={refreshing}
|
||||
|
||||
@@ -20,6 +20,7 @@ interface Props {
|
||||
isFetching?: boolean;
|
||||
isInitialLoading?: boolean;
|
||||
isConnected: boolean | null;
|
||||
loadingTimedOut: boolean;
|
||||
onEndReached: ( ) => void;
|
||||
onRefresh: ( ) => void;
|
||||
refreshing: boolean;
|
||||
@@ -39,6 +40,7 @@ const NotificationsList = ( {
|
||||
isFetching,
|
||||
isInitialLoading,
|
||||
isConnected,
|
||||
loadingTimedOut,
|
||||
onEndReached,
|
||||
onRefresh,
|
||||
reload,
|
||||
@@ -59,6 +61,12 @@ const NotificationsList = ( {
|
||||
), [isFetching, isConnected, data.length] );
|
||||
|
||||
const renderEmptyComponent = useCallback( ( ) => {
|
||||
// show an offline/retry state if the user isn't connected or this request just takes too long
|
||||
if ( isConnected === false || loadingTimedOut ) {
|
||||
return <OfflineNotice onPress={reload} />;
|
||||
}
|
||||
|
||||
// Loading
|
||||
if ( isInitialLoading ) {
|
||||
return (
|
||||
<View className="h-full justify-center">
|
||||
@@ -67,10 +75,7 @@ const NotificationsList = ( {
|
||||
);
|
||||
}
|
||||
|
||||
if ( isConnected === false ) {
|
||||
return <OfflineNotice onPress={reload} />;
|
||||
}
|
||||
|
||||
// Empty/error state
|
||||
let msg = t( "No-Notifications-Found" );
|
||||
let msg2 = null;
|
||||
if ( !currentUser ) {
|
||||
@@ -92,6 +97,7 @@ const NotificationsList = ( {
|
||||
isError,
|
||||
isInitialLoading,
|
||||
isConnected,
|
||||
loadingTimedOut,
|
||||
reload,
|
||||
t,
|
||||
] );
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { fetchObservationUpdates, fetchRemoteObservations } from "api/observations";
|
||||
import type {
|
||||
ApiNotification,
|
||||
@@ -7,12 +8,15 @@ import type {
|
||||
} from "api/types";
|
||||
import flatten from "lodash/flatten";
|
||||
import { RealmContext } from "providers/contexts";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import type Realm from "realm";
|
||||
import Observation from "realmModels/Observation";
|
||||
import { useAuthenticatedInfiniteQuery, useCurrentUser } from "sharedHooks";
|
||||
|
||||
const { useRealm } = RealmContext;
|
||||
|
||||
const LOADING_TIMEOUT = 5000;
|
||||
|
||||
// Extends API response with data we need in this app
|
||||
export interface Notification extends ApiNotification {
|
||||
resource?: ApiObservation;
|
||||
@@ -24,6 +28,7 @@ interface InfiniteNotificationsScrollResponse {
|
||||
isError?: boolean;
|
||||
isFetching?: boolean;
|
||||
isInitialLoading?: boolean;
|
||||
loadingTimedOut: boolean;
|
||||
notifications: Notification[];
|
||||
refetch: ( ) => void;
|
||||
}
|
||||
@@ -89,10 +94,22 @@ const useInfiniteNotificationsScroll = (
|
||||
): InfiniteNotificationsScrollResponse => {
|
||||
const currentUser = useCurrentUser( );
|
||||
const realm = useRealm( );
|
||||
const queryClient = useQueryClient();
|
||||
const [loadingTimedOut, setLoadingTimedOut] = useState( false );
|
||||
|
||||
const queryKey = ["useInfiniteNotificationsScroll", JSON.stringify( notificationParams )];
|
||||
const queryKey = useMemo(
|
||||
() => ["useInfiniteNotificationsScroll", JSON.stringify( notificationParams )],
|
||||
[notificationParams],
|
||||
);
|
||||
|
||||
const infQueryResult = useAuthenticatedInfiniteQuery(
|
||||
const {
|
||||
data,
|
||||
isFetching,
|
||||
isInitialLoading,
|
||||
isError,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
} = useAuthenticatedInfiniteQuery(
|
||||
queryKey,
|
||||
async ( { pageParam }: { pageParam: number }, optsWithAuth: ApiOpts ) => {
|
||||
const params = { ...BASE_PARAMS, ...notificationParams };
|
||||
@@ -142,16 +159,49 @@ const useInfiniteNotificationsScroll = (
|
||||
},
|
||||
);
|
||||
|
||||
// We want to timeout and show an offline/retry state if this request takes too long
|
||||
useEffect( () => {
|
||||
// Reset if we get data
|
||||
if ( data !== undefined && !isFetching ) {
|
||||
setLoadingTimedOut( false );
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Don't set timer if not loading
|
||||
if ( !isFetching ) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Set a timeout and cancel the query if we hit the limit
|
||||
const timer = setTimeout( () => {
|
||||
if ( data === undefined && isFetching ) {
|
||||
queryClient.cancelQueries( { queryKey } );
|
||||
setLoadingTimedOut( true );
|
||||
}
|
||||
}, LOADING_TIMEOUT );
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return () => clearTimeout( timer );
|
||||
}, [data, isFetching, queryKey, queryClient] );
|
||||
|
||||
// Reset when user manually retries
|
||||
useEffect( () => {
|
||||
if ( isFetching ) {
|
||||
setLoadingTimedOut( false );
|
||||
}
|
||||
}, [isFetching] );
|
||||
|
||||
return {
|
||||
refetch: infQueryResult.refetch,
|
||||
isError: infQueryResult.isError,
|
||||
isFetching: infQueryResult.isFetching,
|
||||
isInitialLoading: infQueryResult.isInitialLoading,
|
||||
refetch,
|
||||
isError,
|
||||
isFetching,
|
||||
isInitialLoading,
|
||||
loadingTimedOut,
|
||||
// Disable fetchNextPage if signed out
|
||||
fetchNextPage: currentUser
|
||||
? infQueryResult.fetchNextPage
|
||||
? fetchNextPage
|
||||
: ( ) => undefined,
|
||||
notifications: flatten( infQueryResult?.data?.pages ),
|
||||
notifications: flatten( data?.pages ),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user