Use stable array/object constants

This commit is contained in:
schroda
2026-03-20 03:02:52 +01:00
parent fcf1c7fa3f
commit 9ce273b5ce
19 changed files with 44 additions and 24 deletions

View File

@@ -8,6 +8,7 @@
import type { Ref } from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
@@ -61,7 +62,7 @@ export const SpinnerImage = ({ ref, ...props }: SpinnerImageProps) => {
alt,
onLoad,
onError,
spinnerStyle: { small, ...spinnerStyle } = {},
spinnerStyle: { small, ...spinnerStyle } = STABLE_EMPTY_OBJECT,
imgStyle,
hideImgStyle,
priority,

View File

@@ -10,11 +10,12 @@ import Box from '@mui/material/Box';
import type { CircularProgressProps } from '@mui/material/CircularProgress';
import CircularProgress from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';
import { STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
export const Progress = ({
progress,
showText = true,
progressProps = {},
progressProps = STABLE_EMPTY_OBJECT,
}: {
progress: number;
showText?: boolean;

View File

@@ -12,6 +12,7 @@
* with a few changes to fix a bug on mobile devices where opening the sub menu immediately triggered the on click of the underlying menu item
*/
import { STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
import type { MenuProps as MuiMenuProps } from '@mui/material/Menu';
import Menu from '@mui/material/Menu';
import type { MenuItemProps as MuiMenuItemProps } from '@mui/material/MenuItem';
@@ -64,7 +65,7 @@ export const NestedMenuItem = ({ ref, ...props }: NestedMenuItemProps) => {
children,
className,
tabIndex: tabIndexProp,
ContainerProps: ContainerPropsProp = {},
ContainerProps: ContainerPropsProp = STABLE_EMPTY_OBJECT,
MenuProps,
...MenuItemProps
} = props;

View File

@@ -8,6 +8,7 @@
import type { RefObject } from 'react';
import { useLayoutEffect, useState } from 'react';
import { STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
export const useIntersectionObserver = (
ref: RefObject<HTMLElement | null> | HTMLElement | undefined | null,
@@ -17,7 +18,7 @@ export const useIntersectionObserver = (
root,
rootMargin,
threshold,
}: IntersectionObserverInit & { ignoreInitialObserve?: boolean } = {},
}: IntersectionObserverInit & { ignoreInitialObserve?: boolean } = STABLE_EMPTY_OBJECT,
): (() => void) => {
const [disconnect, setDisconnect] = useState<() => void>(() => {});

View File

@@ -46,6 +46,7 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { makeToast } from '@/base/utils/Toast.ts';
import { ChapterListCard } from '@/features/chapter/components/cards/ChapterListCard.tsx';
import { VirtuosoPersisted } from '@/lib/virtuoso/Component/VirtuosoPersisted.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
type ChapterListHeaderProps = {
scrollbarWidth: number;
@@ -140,7 +141,7 @@ export const ChapterList = ({
manga.id,
{ notifyOnNetworkStatusChange: true },
);
const chapters = useMemo(() => chaptersData?.chapters.nodes ?? [], [chaptersData?.chapters.nodes]);
const chapters = chaptersData?.chapters.nodes ?? STABLE_EMPTY_ARRAY;
const visibleChapters = useMemo(() => filterAndSortChapters(chapters, options), [chapters, options]);
const visibleChapterIds = useMemo(() => Chapters.getIds(visibleChapters), [visibleChapters]);

View File

@@ -6,6 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
import CheckBoxOutlineBlank from '@mui/icons-material/CheckBoxOutlineBlank';
import Delete from '@mui/icons-material/Delete';
import Download from '@mui/icons-material/Download';
@@ -71,7 +72,7 @@ export const ChapterActionMenuItems = ({
chapter,
handleSelection,
canBeDownloaded = false,
selectedChapters = [],
selectedChapters = STABLE_EMPTY_ARRAY,
onClose,
selectable = true,
}: Props) => {

View File

@@ -33,6 +33,7 @@ import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
import { useAppAction } from '@/features/navigation-bar/hooks/useAppAction.ts';
import type { ChapterDownloadStatus } from '@/features/chapter/Chapter.types.ts';
import { VirtuosoPersisted } from '@/lib/virtuoso/Component/VirtuosoPersisted.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
export const DownloadQueue: React.FC = () => {
const { t } = useLingui();
@@ -49,7 +50,7 @@ export const DownloadQueue: React.FC = () => {
} = requestManager.useGetDownloadStatus({ notifyOnNetworkStatusChange: true });
const downloaderData = downloadStatusData?.downloadStatus;
const queue = downloaderData?.queue ?? [];
const queue = downloaderData?.queue ?? STABLE_EMPTY_ARRAY;
const status = downloaderData?.state ?? DownloaderState.Started;
const isQueueEmpty = !queue.length;

View File

@@ -28,6 +28,7 @@ import { AppbarSearch } from '@/base/components/AppbarSearch.tsx';
import { useDebounce } from '@/base/hooks/useDebounce.ts';
import type { MangaCardProps } from '@/features/manga/Manga.types.ts';
import { EmptyView } from '@/base/components/feedback/EmptyView.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { BaseMangaGrid } from '@/features/manga/components/BaseMangaGrid.tsx';
@@ -254,7 +255,7 @@ export const SearchAll: React.FC = () => {
} = useMetadataServerSettings();
const { data, loading, error, refetch } = requestManager.useGetSourceList({ notifyOnNetworkStatusChange: true });
const sources = useMemo(() => data?.sources.nodes ?? [], [data?.sources.nodes]);
const sources = data?.sources.nodes ?? STABLE_EMPTY_ARRAY;
const [sourceToLoadingStateMap, setSourceToLoadingStateMap] = useState<SourceToLoadingStateMap>(new Map());
const debouncedSourceToLoadingStateMap = useDebounce(sourceToLoadingStateMap, 500);
@@ -304,7 +305,7 @@ export const SearchAll: React.FC = () => {
selectedLanguages={shownLangs}
setSelectedLanguages={setShownLangs}
languages={sourceLanguages}
sources={sources ?? []}
sources={sources}
/>
</>,
[shownLangs, setShownLangs, sourceLanguages, sources],

View File

@@ -21,6 +21,7 @@ import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { ChapterHistoryCard } from '@/features/history/components/ChapterHistoryCard.tsx';
import { Chapters } from '@/features/chapter/services/Chapters.ts';
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
export const History: React.FC = () => {
const { t } = useLingui();
@@ -39,7 +40,7 @@ export const History: React.FC = () => {
});
const hasNextPage = !!chapterHistoryData?.chapters.pageInfo.hasNextPage;
const endCursor = chapterHistoryData?.chapters.pageInfo.endCursor;
const readEntries = chapterHistoryData?.chapters.nodes ?? [];
const readEntries = chapterHistoryData?.chapters.nodes ?? STABLE_EMPTY_ARRAY;
const groupedHistory = useMemo(
() => Object.entries(Chapters.groupByDate(readEntries, 'lastReadAt')),
[readEntries],

View File

@@ -50,6 +50,7 @@ import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
import { useAppAction } from '@/features/navigation-bar/hooks/useAppAction.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { SearchParam } from '@/base/Base.types.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
const TitleWithSizeTag = styled('span')({
display: 'flex',
@@ -82,7 +83,7 @@ export function Library() {
const tabsData = categoriesResponse?.categories.nodes.filter(
(category) => category.id !== 0 || (category.id === 0 && category.mangas.totalCount),
);
const tabs = tabsData ?? [];
const tabs = tabsData ?? STABLE_EMPTY_ARRAY;
const librarySizeResponse = requestManager.useGetMangas<
GetLibraryMangaCountQuery,
@@ -102,7 +103,7 @@ export function Library() {
loading: mangaLoading,
refetch: refetchCategoryMangas,
} = requestManager.useGetCategoryMangas(activeTab?.id, { skip: !activeTab, notifyOnNetworkStatusChange: true });
const categoryMangas = categoryMangaResponse?.mangas.nodes ?? [];
const categoryMangas = categoryMangaResponse?.mangas.nodes ?? STABLE_EMPTY_ARRAY;
const {
visibleMangas: mangas,
showFilteredOutMessage,
@@ -127,6 +128,7 @@ export function Library() {
} = useSelectableCollection<MangaType['id'], string>(mangas.length, {
itemIds: mangaIds,
currentKey: activeTab?.id.toString(),
initialState: undefined,
});
const handleSelect: typeof handleSelection = useCallback(

View File

@@ -37,6 +37,7 @@ import type { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaUnreadInfo } fro
import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { CategorySelect } from '@/features/category/components/CategorySelect.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
type BaseProps = { onClose: () => void; setHideMenu: (hide: boolean) => void };
@@ -65,7 +66,7 @@ export const MangaActionMenuItems = ({
const [isTrackDialogOpen, setIsTrackDialogOpen] = useState(false);
const isSingleMode = !!manga;
const selectedMangas = passedSelectedMangas ?? [];
const selectedMangas = passedSelectedMangas ?? STABLE_EMPTY_ARRAY;
const getMenuItemTitle = createGetMenuItemTitle(isSingleMode, MANGA_ACTION_TO_TRANSLATION);
const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode);

View File

@@ -9,8 +9,9 @@
import { useEffect } from 'react';
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
export const useAppAction = (action: NavbarContextType['action'], dependencies: any[] = []) => {
export const useAppAction = (action: NavbarContextType['action'], dependencies: any[] = STABLE_EMPTY_ARRAY) => {
const { setAction } = useNavBarContext();
useEffect(() => {

View File

@@ -6,6 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import MenuItem from '@mui/material/MenuItem';
@@ -32,7 +33,7 @@ const BaseReaderNavBarDesktopChapterNavigation = ({
currentChapterNumber,
previousChapter,
nextChapter,
chapters = [],
chapters = STABLE_EMPTY_ARRAY,
readerThemeDirection,
}: {
currentChapterId: ChapterIdInfo['id'] | undefined;

View File

@@ -6,6 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
import FilterListIcon from '@mui/icons-material/FilterList';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
@@ -159,7 +160,7 @@ export function Options({ sourceFilter, group, updateFilterValue, update }: IFil
}
export function SourceOptions({
savedSearches = {},
savedSearches = STABLE_EMPTY_OBJECT,
selectSavedSearch,
updateSavedSearches,
sourceFilter,

View File

@@ -52,6 +52,7 @@ import { GridLayout, SearchParam } from '@/base/Base.types';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { Sources } from '@/features/source/services/Sources.ts';
import { STABLE_EMPTY_ARRAY, STABLE_EMPTY_OBJECT } from '@/base/Base.constants.ts';
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
import { VirtuosoUtil } from '@/lib/virtuoso/Virtuoso.util.tsx';
@@ -219,7 +220,7 @@ export function SourceMangas() {
useLocation<{
contentType: SourceContentType;
clearCache: boolean;
}>().state ?? {};
}>().state ?? STABLE_EMPTY_OBJECT;
const {
settings: { hideLibraryEntries },
@@ -288,7 +289,7 @@ export function SourceMangas() {
{ data, error, isLoading: loading, size: lastPageNum, abortRequest, filteredOutAllItemsOfFetchedPage },
] = useSourceManga(sourceId, contentType, query, filtersToApply, 1, hideLibraryEntries);
currentAbortRequest.current = abortRequest;
const mangas = data?.fetchSourceManga?.mangas ?? [];
const mangas = data?.fetchSourceManga?.mangas ?? STABLE_EMPTY_ARRAY;
const hasNextPage = !!data?.fetchSourceManga?.hasNextPage;
const isLoading = loading || (filteredOutAllItemsOfFetchedPage && hasNextPage);
const { data: sourceData } = requestManager.useGetSource<GetSourceBrowseQuery, GetSourceBrowseQueryVariables>(
@@ -297,7 +298,7 @@ export function SourceMangas() {
);
const source = sourceData?.source;
const filters = source?.filters ?? [];
const filters = source?.filters ?? STABLE_EMPTY_ARRAY;
const { savedSearches = {} } = useGetSourceMetadata(source ?? DEFAULT_SOURCE);
const updateSourceMetadata = createUpdateSourceMetadata<'savedSearches'>(source ?? { id: '-1' }, (e) =>
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),

View File

@@ -28,6 +28,7 @@ import { makeToast } from '@/base/utils/Toast.ts';
import type { PreferenceProps } from '@/features/source/Source.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
function getPrefComponent(type: string) {
switch (type) {
@@ -58,7 +59,7 @@ export function SourceConfigure() {
>(GET_SOURCE_SETTINGS, sourceId, {
notifyOnNetworkStatusChange: true,
});
const sourcePreferences = data?.source.preferences ?? [];
const sourcePreferences = data?.source.preferences ?? STABLE_EMPTY_ARRAY;
const updateValue =
(position: number): PreferenceProps['updateValue'] =>

View File

@@ -24,6 +24,7 @@ import type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { EmptyView } from '@/base/components/feedback/EmptyView.tsx';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
const getTrackerMode = (id: number, trackersInUse: number[], searchModeForTracker?: number): TrackerMode => {
if (id === searchModeForTracker) {
@@ -46,13 +47,13 @@ export const TrackManga = ({ manga }: { manga: MangaIdInfo & Pick<MangaType, 'ti
const trackerList = requestManager.useGetTrackerList<GetTrackersBindQuery>(GET_TRACKERS_BIND, {
notifyOnNetworkStatusChange: true,
});
const trackers = trackerList.data?.trackers.nodes ?? [];
const trackers = trackerList.data?.trackers.nodes ?? STABLE_EMPTY_ARRAY;
const mangaTrackRecordsList = requestManager.useGetManga<GetMangaTrackRecordsQuery>(
GET_MANGA_TRACK_RECORDS,
manga.id,
);
const mangaTrackRecords = mangaTrackRecordsList.data?.manga.trackRecords.nodes ?? [];
const mangaTrackRecords = mangaTrackRecordsList.data?.manga.trackRecords.nodes ?? STABLE_EMPTY_ARRAY;
const loggedInTrackers = Trackers.getLoggedIn(trackers);
const trackersInUse = Trackers.getLoggedIn(Trackers.getTrackers(mangaTrackRecords, trackers));

View File

@@ -27,6 +27,7 @@ import type { GetTrackersSettingsQuery } from '@/lib/graphql/generated/graphql.t
import type { MetadataTrackingSettings } from '@/features/tracker/Tracker.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
export const TrackingSettings = () => {
const { t } = useLingui();
@@ -50,7 +51,7 @@ export const TrackingSettings = () => {
} = requestManager.useGetTrackerList<GetTrackersSettingsQuery>(GET_TRACKERS_SETTINGS, {
notifyOnNetworkStatusChange: true,
});
const trackers = data?.trackers.nodes ?? [];
const trackers = data?.trackers.nodes ?? STABLE_EMPTY_ARRAY;
const loading = areMetadataServerSettingsLoading || areTrackersLoading;
const error = metadataServerSettingsError ?? trackersError;

View File

@@ -25,6 +25,7 @@ import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
import { Chapters } from '@/features/chapter/services/Chapters.ts';
import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts';
import { GROUPED_VIRTUOSO_Z_INDEX } from '@/lib/virtuoso/Virtuoso.constants.ts';
import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts';
export const Updates: React.FC = () => {
const { t } = useLingui();
@@ -42,7 +43,7 @@ export const Updates: React.FC = () => {
});
const hasNextPage = !!chapterUpdateData?.chapters.pageInfo.hasNextPage;
const endCursor = chapterUpdateData?.chapters.pageInfo.endCursor;
const updateEntries = chapterUpdateData?.chapters.nodes ?? [];
const updateEntries = chapterUpdateData?.chapters.nodes ?? STABLE_EMPTY_ARRAY;
const groupedUpdates = useMemo(
() => Object.entries(Chapters.groupByDate(updateEntries, 'fetchedAt')),
[updateEntries],