From 49c1d9e460e20731c7e9238e2cd986cca6bbdbb1 Mon Sep 17 00:00:00 2001 From: Joshwin Greene Date: Thu, 12 Jan 2023 14:21:15 -0800 Subject: [PATCH] Resolves a merge conflict. Rebase. This commit includes the following commits: feat(various): Created a hook out of the NetworkDetector component feat(various): Refactored the NetworkDetector component so that it shows a barrier if the user goes offline; Moved the use of the useNetworkDetection hook to the NetworkDetector component feat(notificationApi): Made the notificationApi file consistent with the other api files when it comes to refetching data feat(store): Data is now being refetched when the user's connection is restored feat(useReducerInfiniteLoading): In the middle of creating a version of useInfiniteLoading that uses the reducer from useLiveNotifications feat(various): Added the useNewLiveNotifications hook for testing purposes and resolves some issues feat(useReducerInfiniteLoading): The user's unread notifications are now cleared before refetching in the internet reconnection scenario. fix(useReducerInfiniteLoading): Resolved the issue that was causing the notifications to be cleared when more notifications are fetched fix(various): Removed the clearing that was happening when the notification dropdown was closed. This is already handled by the useReducerInfiniteLoading hook. refactor(various): Renamed notification to item in the useReducerInfiniteLoading hook refactor(useReducerInfiniteLoading): Implemented a better solution for the duplicate item issue when the user's internet connection is restored refactor(useReducerInfiniteLoading): Removed some unnecessary code and added some console logs in order to help identify why this hook keeps executing fix(useReducerInfiniteLoading): Resolves the infinite re-rendering of the notification listview fix(useReducerInfiniteLoading): Corrected the useReducerInfiniteLoading hook so that it returns the right type for the items and made it be compatible with the pages that use the useInfiniteLoading hook refactor(various): Now using the useReducerInfiniteLoading hook in all of the places where the useInfiniteLoading hook was being used fix(useReducerInfiniteLoading): Forget to add the error value to the useMemo dependency list fix(useReducerInfiniteLoading): The nextItemUrl wasn't being set correctly when the reducer state was reset. fix(various): I found that the resetApiState call would cause the infinite loading functionality to just refresh the whole page instead of working as you would aspect. Removing it fixed this issue. Based on my testing, I found it wasn't necessary for the notification functionality. fix(various): It was incorrect to remove the resetApiState function call for the notification functionality. refactor(various): Removed the old versions of useLiveNotifications and useInfiniteLoading and replaced them with the new ones refactored(useLiveNotifications): Removed some commented out code refactor(NetworkDetector): Removed the InteractionBarrier since the service worker PR will make this functionality unnecessary refactor(various): Renamed WithNumberIdentifier to WithIdentifier and made its id property accept string values as well refactor(various): In the middle of making the infinite loading functionality simpler refactor(various): Mostly everything is working. Just need to get the count on the NotificationListView to update when a notification is removed. feat(various): The count on the NotificationListView is now being updated correctly. refactor(various): Renamed the addOne and addMultiple cases refactor(useInfinteLoading): Need to implement a better solution for updating the count on NotificationListView refactor(various): The count is now being updated correctly again. refactor(various): Removed unnecessary properties and console logs; Resolved eslint issues --- src/app/redux/store.ts | 3 + src/common/api/notificationApi.ts | 7 + src/common/hooks/useInfiniteLoading.ts | 159 +++++++++++++----- .../farm-dashboard/pages/UpdateFarmView.tsx | 19 ++- .../components/NetworkDetector.tsx | 33 +--- .../hooks/useNetworkConnection.ts | 35 ++++ .../components/NotificationDropdown.tsx | 16 +- .../components/ReadNotifications.tsx | 14 +- .../hooks/useLiveNotifications.tsx | 114 ++----------- .../user-dashboard/pages/UpdateUserView.tsx | 21 ++- 10 files changed, 225 insertions(+), 196 deletions(-) create mode 100644 src/features/network-detector/hooks/useNetworkConnection.ts diff --git a/src/app/redux/store.ts b/src/app/redux/store.ts index 68e77c820..5ba6285fb 100644 --- a/src/app/redux/store.ts +++ b/src/app/redux/store.ts @@ -1,5 +1,6 @@ import { configureStore, ConfigureStoreOptions } from '@reduxjs/toolkit'; import { farmApi } from 'common/api/farmApi'; +import { setupListeners } from '@reduxjs/toolkit/query'; import { authApi } from 'common/api/authApi'; import { notificationApi } from 'common/api/notificationApi'; import { userApi } from 'common/api/userApi'; @@ -31,6 +32,8 @@ export const createAppStore = (options?: ConfigureStoreOptions['preloadedState'] export const store = createAppStore(); +setupListeners(store.dispatch); + export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch; diff --git a/src/common/api/notificationApi.ts b/src/common/api/notificationApi.ts index 86fce5e21..6021733db 100644 --- a/src/common/api/notificationApi.ts +++ b/src/common/api/notificationApi.ts @@ -5,7 +5,14 @@ import { customBaseQuery } from './customBaseQuery'; export const notificationApi = createApi({ reducerPath: 'notificationApi', + baseQuery: customBaseQuery, + + // Always refetch data, don't used cache. + keepUnusedDataFor: 0, + refetchOnMountOrArgChange: true, + refetchOnReconnect: true, + tagTypes: ['AppNotification'], endpoints: builder => ({ diff --git a/src/common/hooks/useInfiniteLoading.ts b/src/common/hooks/useInfiniteLoading.ts index 6549fa77a..458312227 100644 --- a/src/common/hooks/useInfiniteLoading.ts +++ b/src/common/hooks/useInfiniteLoading.ts @@ -1,58 +1,135 @@ +import { ActionCreatorWithoutPayload } from '@reduxjs/toolkit'; import { PaginatedResult } from 'common/models'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useReducer } from 'react'; +import { useDispatch } from 'react-redux'; import { UseQuery, UseQueryOptions } from 'rtk-query-config'; -export const useInfiniteLoading = >( - initialUrl: string, +export interface WithIdentifier { + id?: number | string; +} + +interface State { + items: T[]; + nextItemUrl: string | null; + count: number; + isGettingMore: boolean; +} + +const initialState = { + items: [], + nextItemUrl: null, + count: 0, + isGettingMore: false, +}; + +type Action = + | { type: 'addOneToFront'; item: T } + | { type: 'addMultipleToBack'; items: T[]; totalCount: number } + | { type: 'set-next-item-url'; nextItemUrl: string | null } + | { type: 'reset-get-more' } + | { type: 'remove'; item: T } + | { type: 'reset'; nextItemUrl: string | null }; + +const reducer = (state: State, action: Action) => { + switch (action.type) { + case 'addOneToFront': + return { ...state, items: [action.item, ...state.items], count: state.count + 1 }; + case 'addMultipleToBack': + return { ...state, items: [...state.items, ...action.items], count: action.totalCount }; + case 'remove': + return { + ...state, + items: state.items.filter(i => i.id !== action.item.id), + count: state.count - 1, + }; + case 'reset': + return { ...initialState, nextItemUrl: action.nextItemUrl }; + case 'set-next-item-url': + return { ...state, nextItemUrl: action.nextItemUrl, isGettingMore: true, allItemsRemoved: false }; + default: + return { ...initialState }; + } +}; + +export const useInfiniteLoading = >( + initialUrl: string | null, useQuery: UseQuery, + resetApiStateFunction?: ActionCreatorWithoutPayload, options?: UseQueryOptions, ) => { - const [url, setUrl] = useState(initialUrl); - const [loadedData, setLoadedData] = useState([]); - const rerenderingType = useRef('clear'); + const [{ items, nextItemUrl, count, isGettingMore }, itemDispatch] = useReducer(reducer, { + ...initialState, + nextItemUrl: initialUrl, + }); + const dispatch = useDispatch(); - const { data, error, isLoading, isFetching } = useQuery(url, options); + const { data: fetchedItems, isFetching, isLoading, refetch, error } = useQuery(nextItemUrl, options); - useEffect(() => { - const clear = () => { - rerenderingType.current = 'clear'; - setLoadedData([]); - setUrl(initialUrl); - }; + const addOneToFront = useCallback( + (newItem: T) => { + itemDispatch({ type: 'addOneToFront', item: newItem }); + }, + [itemDispatch], + ); - if (data && !isLoading) { - setLoadedData(n => [...n, ...data.results]); + const clear = useCallback(() => { + itemDispatch({ type: 'reset', nextItemUrl: initialUrl }); + if (resetApiStateFunction) { + dispatch(resetApiStateFunction()); } + }, [itemDispatch, initialUrl, dispatch, resetApiStateFunction]); - return () => { - if (rerenderingType.current === 'clear') { - clear(); - } - if (rerenderingType.current === 'fetchMore') { - rerenderingType.current = 'clear'; - } - }; - }, [data, isLoading, initialUrl]); + const remove = useCallback( + (itemToRemove: T) => { + itemDispatch({ type: 'remove', item: itemToRemove }); + }, + [itemDispatch], + ); const hasMore = useMemo(() => { if (isLoading || isFetching) return false; - return !!data?.links.next; - }, [data, isLoading, isFetching]); + return !!fetchedItems?.links.next; + }, [fetchedItems, isLoading, isFetching]); - const fetchMore = () => { - if (hasMore && data) { - rerenderingType.current = 'fetchMore'; - setUrl(data.links.next); + const getMore = useCallback(() => { + if (fetchedItems?.links.next && !isFetching) { + itemDispatch({ type: 'set-next-item-url', nextItemUrl: fetchedItems.links.next }); } - }; - - return { - loadedData, - error, - isLoading, - isFetching, - totalCount: data?.meta.count, - hasMore, - fetchMore, - }; + }, [itemDispatch, isFetching, fetchedItems]); + + // Clear the items when the user's internet connection is restored + useEffect(() => { + if (!isLoading && isFetching && !isGettingMore) { + clear(); + } + }, [isLoading, isFetching, isGettingMore, clear]); + + // Append new items that we got from the API to + // the items list + useEffect(() => { + itemDispatch({ + type: 'addMultipleToBack', + items: fetchedItems?.results || [], + totalCount: fetchedItems?.meta.count || 0, + }); + }, [fetchedItems]); + + const itemProviderValue = useMemo(() => { + const result = { + items: items as T[], + count, + hasMore, + isFetching, + isLoading, + remove, + clear, + getMore, + refetch, + addOneToFront, + error, + }; + return result; + }, [clear, remove, getMore, hasMore, items, count, isFetching, isLoading, addOneToFront, refetch, error]); + + return itemProviderValue; }; diff --git a/src/features/farm-dashboard/pages/UpdateFarmView.tsx b/src/features/farm-dashboard/pages/UpdateFarmView.tsx index 246bbb697..e7a51b6b6 100644 --- a/src/features/farm-dashboard/pages/UpdateFarmView.tsx +++ b/src/features/farm-dashboard/pages/UpdateFarmView.tsx @@ -12,13 +12,13 @@ import { Link, useNavigate, useParams } from 'react-router-dom'; import { FarmDetailForm, FormData } from '../components/FarmDetailForm'; import { useAuth } from 'features/auth/hooks'; import { ChangeLog } from 'common/components/ChangeLog/ChangeLog'; -import { useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; import { QueryParamsBuilder } from 'common/api/queryParamsBuilder'; import { HistoricalRecord } from 'common/models/historicalRecord'; import { ChangeListGroup } from 'common/components/ChangeLog/ChangeListGroup'; import { LoadingButton } from 'common/components/LoadingButton'; import { useModal } from 'react-modal-hook'; import { DimmableContent } from 'common/styles/utilities'; +import { WithIdentifier, useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; export type RouteParams = { id: string; @@ -35,15 +35,18 @@ export const UpdateFarmView: FC = () => { const queryParams = new QueryParamsBuilder().setPaginationParams(1, pageSize).build(); const url = `/farms/${id}/history/?${queryParams}`; const { - loadedData: farmHistory, + items: farmHistory, error: farmHistoryError, isFetching: isFetchingHistory, - totalCount, + count: totalCount, hasMore, - fetchMore, - } = useInfiniteLoading, PaginatedResult>>(url, useGetFarmHistoryQuery, { - skip: user?.role !== 'ADMIN', - }); + getMore, + } = useInfiniteLoading & WithIdentifier, PaginatedResult>>( + url, + useGetFarmHistoryQuery, + undefined, + { skip: user?.role !== 'ADMIN' }, + ); const [formValidationErrors, setFormValidationErrors] = useState | null>(null); @@ -65,7 +68,7 @@ export const UpdateFarmView: FC = () => { className='action-shadow' loading={isFetchingHistory} variant='primary' - onClick={() => fetchMore()} + onClick={() => getMore()} > Load More diff --git a/src/features/network-detector/components/NetworkDetector.tsx b/src/features/network-detector/components/NetworkDetector.tsx index d97fd1318..b3cd4fdd4 100644 --- a/src/features/network-detector/components/NetworkDetector.tsx +++ b/src/features/network-detector/components/NetworkDetector.tsx @@ -1,35 +1,8 @@ -import { FC, PropsWithChildren, useEffect, useRef, useState } from 'react'; -import * as notificationService from 'common/services/notification'; +import { FC, PropsWithChildren } from 'react'; +import { useNetworkDetection } from '../hooks/useNetworkConnection'; export const NetworkDetector: FC> = ({ children }) => { - const [isDisconnected, setDisconnectedStatus] = useState(false); - const prevDisconnectionStatus = useRef(false); - - const handleConnectionChange = () => { - setDisconnectedStatus(!navigator.onLine); - }; - - const getRandomNumber = () => { - return new Date().valueOf().toString(); - }; - - useEffect(() => { - window.addEventListener('online', handleConnectionChange); - window.addEventListener('offline', handleConnectionChange); - - if (isDisconnected) { - notificationService.showErrorMessage('Internet Connection Lost', getRandomNumber()); - } else if (prevDisconnectionStatus.current) { - notificationService.showSuccessMessage('Internet Connection Restored', getRandomNumber()); - } - - prevDisconnectionStatus.current = isDisconnected; - - return () => { - window.removeEventListener('online', handleConnectionChange); - window.removeEventListener('offline', handleConnectionChange); - }; - }, [isDisconnected]); + useNetworkDetection(); return <>{children}; }; diff --git a/src/features/network-detector/hooks/useNetworkConnection.ts b/src/features/network-detector/hooks/useNetworkConnection.ts new file mode 100644 index 000000000..899faffd2 --- /dev/null +++ b/src/features/network-detector/hooks/useNetworkConnection.ts @@ -0,0 +1,35 @@ +import { useState, useRef, useEffect } from 'react'; +import * as notificationService from 'common/services/notification'; + +export const getRandomNumber = () => { + return new Date().valueOf().toString(); +}; + +export const useNetworkDetection = () => { + const [isDisconnected, setDisconnectedStatus] = useState(false); + const prevDisconnectionStatus = useRef(false); + + const handleConnectionChange = () => { + setDisconnectedStatus(!navigator.onLine); + }; + + useEffect(() => { + window.addEventListener('online', handleConnectionChange); + window.addEventListener('offline', handleConnectionChange); + + if (isDisconnected) { + notificationService.showErrorMessage('Internet Connection Lost', getRandomNumber()); + } else if (prevDisconnectionStatus.current) { + notificationService.showSuccessMessage('Internet Connection Restored', getRandomNumber()); + } + + prevDisconnectionStatus.current = isDisconnected; + + return () => { + window.removeEventListener('online', handleConnectionChange); + window.removeEventListener('offline', handleConnectionChange); + }; + }, [isDisconnected]); + + return { isDisconnected }; +}; diff --git a/src/features/notifications/components/NotificationDropdown.tsx b/src/features/notifications/components/NotificationDropdown.tsx index 598463e58..21e9c46a9 100644 --- a/src/features/notifications/components/NotificationDropdown.tsx +++ b/src/features/notifications/components/NotificationDropdown.tsx @@ -2,7 +2,7 @@ import { faBell, faEnvelope, faEnvelopeOpen } from '@fortawesome/free-solid-svg- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useGetReadNotificationsQuery, useMarkAllReadMutation } from 'common/api/notificationApi'; import { LoadingButton } from 'common/components/LoadingButton'; -import { useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; +import { WithIdentifier, useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; import { PaginatedResult } from 'common/models'; import { AppNotification } from 'common/models/notifications'; import { NoContent } from 'common/styles/utilities'; @@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { NotificationContext } from '../context'; import { renderNotification } from './renderNotification'; +import { notificationApi } from 'common/api/notificationApi'; const StyledContainer = styled.div` min-width: 420px; @@ -72,10 +73,15 @@ export const NotificationDropdown: FC = () => { count: unreadNotificationsCount, clear: clearUnreadNotifications, } = useContext(NotificationContext); - const { loadedData: readNotifications, isLoading: isLoadingReadNotifications } = useInfiniteLoading< - AppNotification, - PaginatedResult - >('', useGetReadNotificationsQuery); + const { + items: readNotifications, + isLoading: isLoadingReadNotifications, + hasMore: hasMoreReadNotifications, + } = useInfiniteLoading>( + '', + useGetReadNotificationsQuery, + notificationApi.util.resetApiState, + ); const [markAllRead, { isLoading: isLoadingMarkAllRead }] = useMarkAllReadMutation(); const handleMarkAllRead = async () => { diff --git a/src/features/notifications/components/ReadNotifications.tsx b/src/features/notifications/components/ReadNotifications.tsx index 936b8e4f9..e1934fc48 100644 --- a/src/features/notifications/components/ReadNotifications.tsx +++ b/src/features/notifications/components/ReadNotifications.tsx @@ -1,5 +1,5 @@ import { faBell } from '@fortawesome/free-solid-svg-icons'; -import { useGetReadNotificationsQuery } from 'common/api/notificationApi'; +import { notificationApi, useGetReadNotificationsQuery } from 'common/api/notificationApi'; import { useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; import { PaginatedResult } from 'common/models'; import { AppNotification } from 'common/models/notifications'; @@ -10,12 +10,16 @@ import { renderNotification } from './renderNotification'; export const ReadNotifications: FC = () => { const { - loadedData: notifications, + items: notifications, isLoading, isFetching, hasMore, - fetchMore, - } = useInfiniteLoading>('', useGetReadNotificationsQuery); + getMore, + } = useInfiniteLoading>( + '', + useGetReadNotificationsQuery, + notificationApi.util.resetApiState, + ); return ( <> @@ -34,7 +38,7 @@ export const ReadNotifications: FC = () => { {hasMore && (
-
diff --git a/src/features/notifications/hooks/useLiveNotifications.tsx b/src/features/notifications/hooks/useLiveNotifications.tsx index 0dc28eeed..1b26f42ee 100644 --- a/src/features/notifications/hooks/useLiveNotifications.tsx +++ b/src/features/notifications/hooks/useLiveNotifications.tsx @@ -1,77 +1,22 @@ import { notificationApi, useGetEventTokenQuery, useGetUnreadNotificationsQuery } from 'common/api/notificationApi'; +import { useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; +import { PaginatedResult } from 'common/models'; import { AppNotification } from 'common/models/notifications'; import { environment } from 'environment'; import { useAuth } from 'features/auth/hooks'; import * as NotificationComponents from 'features/notifications/components'; -import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { toast } from 'react-toastify'; -type State = { - notifications: AppNotification[]; - oldNotifications: AppNotification[]; - nextNotificationUrl: string | null; - count: number; -}; - -const initialState: State = { - notifications: [], - oldNotifications: [], - nextNotificationUrl: null, - count: 0, -}; - -type Action = - | { type: 'add'; notification: AppNotification } - | { type: 'add-old'; notifications: AppNotification[]; count: number } - | { type: 'set-next-notification-url'; nextNotificationUrl: string | null } - | { type: 'remove'; notification: AppNotification } - | { type: 'reset' }; - -const reducer = (state: State, action: Action) => { - switch (action.type) { - case 'add': - return { ...state, notifications: [action.notification, ...state.notifications] }; - case 'add-old': - return { ...state, oldNotifications: [...state.oldNotifications, ...action.notifications], count: action.count }; - case 'remove': { - const notifications = state.notifications.filter(n => n.id !== action.notification.id); - const oldNotifications = state.oldNotifications.filter(n => n.id !== action.notification.id); - let numRemoved = state.notifications.length - notifications.length; - numRemoved += state.oldNotifications.length - oldNotifications.length; - - return { - ...state, - notifications, - oldNotifications, - count: state.count - numRemoved, - }; - } - case 'reset': - return { ...initialState }; - case 'set-next-notification-url': - return { ...state, nextNotificationUrl: action.nextNotificationUrl }; - default: - return { ...initialState }; - } -}; - export const useLiveNotifications = () => { const { user } = useAuth(); const dispatch = useDispatch(); - const [{ notifications, oldNotifications, nextNotificationUrl, count }, notificationDispatch] = useReducer( - reducer, - initialState, - ); - const { - data: unreadNotifications, - isFetching, - isLoading, - refetch, - } = useGetUnreadNotificationsQuery(nextNotificationUrl, { - skip: !user, - }); + const { items, count, hasMore, getMore, isFetching, isLoading, refetch, clear, addOneToFront, remove } = useInfiniteLoading< + AppNotification, + PaginatedResult + >(null, useGetUnreadNotificationsQuery, notificationApi.util.resetApiState, { skip: !user }); const [enablePolling, setEnablePolling] = useState(true); const { data: eventToken } = useGetEventTokenQuery(undefined, { @@ -79,25 +24,14 @@ export const useLiveNotifications = () => { pollingInterval: enablePolling ? 3000 : 0, }); - // Append new notifications that we got from the API to - // oldNotifications list - useEffect(() => { - notificationDispatch({ - type: 'add-old', - notifications: unreadNotifications?.results || [], - count: unreadNotifications?.meta.count || 0, - }); - }, [unreadNotifications]); - useEffect(() => { if (user) { refetch(); } else { // User no longer logged in. Clean up state. - dispatch(notificationApi.util.resetApiState()); - notificationDispatch({ type: 'reset' }); + clear(); } - }, [user, refetch, notificationDispatch, dispatch]); + }, [user, refetch, clear, dispatch]); // Set up receiving SSE events from the server. useEffect(() => { @@ -108,7 +42,7 @@ export const useLiveNotifications = () => { eventSource.onmessage = message => { const payload = JSON.parse(message.data); - notificationDispatch({ type: 'add', notification: payload }); + addOneToFront(payload); // Grab the component for the notification type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -137,31 +71,13 @@ export const useLiveNotifications = () => { return () => { eventSource?.close(); }; - }, [user, eventToken, setEnablePolling]); - - const clear = useCallback(() => { - notificationDispatch({ type: 'reset' }); - dispatch(notificationApi.util.resetApiState()); - }, [notificationDispatch, dispatch]); - - const remove = useCallback( - (notification: AppNotification) => { - notificationDispatch({ type: 'remove', notification }); - }, - [notificationDispatch], - ); - - const getMore = useCallback(() => { - if (unreadNotifications?.links.next && !isFetching) { - notificationDispatch({ type: 'set-next-notification-url', nextNotificationUrl: unreadNotifications.links.next }); - } - }, [notificationDispatch, isFetching, unreadNotifications]); + }, [user, eventToken, setEnablePolling, addOneToFront]); const notificationProviderValue = useMemo(() => { const result = { - notifications: [...notifications, ...oldNotifications], - count: notifications.length + count, // I'm not so sure this is right. - hasMore: !!unreadNotifications?.links.next, + notifications: items, + count, + hasMore, isFetching, isLoading, remove, @@ -169,7 +85,7 @@ export const useLiveNotifications = () => { getMore, }; return result; - }, [clear, remove, getMore, notifications, unreadNotifications, count, oldNotifications, isFetching, isLoading]); + }, [clear, remove, getMore, hasMore, items, count, isFetching, isLoading]); return notificationProviderValue; }; diff --git a/src/features/user-dashboard/pages/UpdateUserView.tsx b/src/features/user-dashboard/pages/UpdateUserView.tsx index c88060cde..52e45d958 100644 --- a/src/features/user-dashboard/pages/UpdateUserView.tsx +++ b/src/features/user-dashboard/pages/UpdateUserView.tsx @@ -15,7 +15,7 @@ import { Trans } from 'react-i18next'; import { ChangeLog } from 'common/components/ChangeLog/ChangeLog'; import { useAuth } from 'features/auth/hooks'; import { LoadingButton } from 'common/components/LoadingButton'; -import { useInfiniteLoading } from 'common/hooks/useInfiniteLoading'; +import { useInfiniteLoading, WithIdentifier } from 'common/hooks/useInfiniteLoading'; import { HistoricalRecord } from 'common/models/historicalRecord'; import { QueryParamsBuilder } from 'common/api/queryParamsBuilder'; import { ChangeListGroup } from 'common/components/ChangeLog/ChangeListGroup'; @@ -38,15 +38,20 @@ export const UpdateUserView: FC = () => { const queryParams = new QueryParamsBuilder().setPaginationParams(1, pageSize).build(); const url = `/users/${id}/history/?${queryParams}`; const { - loadedData: userHistory, + items: userHistory, error: userHistoryError, isFetching: isFetchingHistory, - totalCount, + count: totalCount, hasMore, - fetchMore, - } = useInfiniteLoading, PaginatedResult>>(url, useGetUserHistoryQuery, { - skip: loggedInUser?.role !== 'ADMIN', - }); + getMore, + } = useInfiniteLoading & WithIdentifier, PaginatedResult>>( + url, + useGetUserHistoryQuery, + undefined, + { + skip: loggedInUser?.role !== 'ADMIN', + }, + ); const roles = Object.values(Role); @@ -71,7 +76,7 @@ export const UpdateUserView: FC = () => { className='action-shadow' loading={isFetchingHistory} variant='primary' - onClick={() => fetchMore()} + onClick={() => getMore()} > Load More