-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
4718d15
commit 49c1d9e
Showing
10 changed files
with
225 additions
and
196 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = <T, ResultType extends PaginatedResult<T>>( | ||
initialUrl: string, | ||
export interface WithIdentifier { | ||
id?: number | string; | ||
} | ||
|
||
interface State<T> { | ||
items: T[]; | ||
nextItemUrl: string | null; | ||
count: number; | ||
isGettingMore: boolean; | ||
} | ||
|
||
const initialState = { | ||
items: [], | ||
nextItemUrl: null, | ||
count: 0, | ||
isGettingMore: false, | ||
}; | ||
|
||
type Action<T> = | ||
| { 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 = <T extends WithIdentifier>(state: State<T>, action: Action<T>) => { | ||
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 = <T extends WithIdentifier, ResultType extends PaginatedResult<T>>( | ||
initialUrl: string | null, | ||
useQuery: UseQuery<ResultType>, | ||
resetApiStateFunction?: ActionCreatorWithoutPayload, | ||
options?: UseQueryOptions, | ||
) => { | ||
const [url, setUrl] = useState<string | null>(initialUrl); | ||
const [loadedData, setLoadedData] = useState<T[]>([]); | ||
const rerenderingType = useRef<string>('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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 3 additions & 30 deletions
33
src/features/network-detector/components/NetworkDetector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PropsWithChildren<unknown>> = ({ 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}</>; | ||
}; |
35 changes: 35 additions & 0 deletions
35
src/features/network-detector/hooks/useNetworkConnection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.