From 39e2212d1de3326b38860e283f52b64f0864d0f9 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 24 Dec 2022 20:20:17 -0800 Subject: [PATCH] Fix refetch on filter change --- .../albums/components/album-list-content.tsx | 11 ++- .../albums/components/album-list-header.tsx | 97 +++++++++++++------ .../albums/routes/album-list-route.tsx | 9 +- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/renderer/features/albums/components/album-list-content.tsx b/src/renderer/features/albums/components/album-list-content.tsx index fc41972c..3031d91a 100644 --- a/src/renderer/features/albums/components/album-list-content.tsx +++ b/src/renderer/features/albums/components/album-list-content.tsx @@ -2,11 +2,12 @@ import { ALBUM_CARD_ROWS, VirtualGridAutoSizerContainer, VirtualInfiniteGrid, + VirtualInfiniteGridRef, } from '/@/renderer/components'; import { AppRoute } from '/@/renderer/router/routes'; import { CardDisplayType, CardRow, LibraryItem } from '/@/renderer/types'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { useCallback, useMemo } from 'react'; +import { MutableRefObject, useCallback, useMemo } from 'react'; import { ListOnScrollProps } from 'react-window'; import { api } from '/@/renderer/api'; import { controller } from '/@/renderer/api/controller'; @@ -17,7 +18,11 @@ import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-han import { useQueryClient } from '@tanstack/react-query'; import { useCurrentServer, useSetAlbumStore, useAlbumListStore } from '/@/renderer/store'; -export const AlbumListContent = () => { +interface AlbumListContentProps { + gridRef: MutableRefObject; +} + +export const AlbumListContent = ({ gridRef }: AlbumListContentProps) => { const queryClient = useQueryClient(); const server = useCurrentServer(); const page = useAlbumListStore(); @@ -135,6 +140,7 @@ export const AlbumListContent = () => { {({ height, width }) => ( { itemSize={150 + page.grid?.size} itemType={LibraryItem.ALBUM} minimumBatchSize={40} - refresh={page.filter} route={{ route: AppRoute.LIBRARY_ALBUMS_DETAIL, slugs: [{ idProperty: 'id', slugProperty: 'albumId' }], diff --git a/src/renderer/features/albums/components/album-list-header.tsx b/src/renderer/features/albums/components/album-list-header.tsx index 722d22d8..a745182e 100644 --- a/src/renderer/features/albums/components/album-list-header.tsx +++ b/src/renderer/features/albums/components/album-list-header.tsx @@ -1,6 +1,7 @@ -import type { MouseEvent, ChangeEvent } from 'react'; +import type { MouseEvent, ChangeEvent, MutableRefObject } from 'react'; import { useCallback } from 'react'; import { Flex, Slider } from '@mantine/core'; +import { useQueryClient } from '@tanstack/react-query'; import debounce from 'lodash/debounce'; import throttle from 'lodash/throttle'; import { @@ -18,18 +19,23 @@ import { Popover, SearchInput, TextTitle, + VirtualInfiniteGridRef, } from '/@/renderer/components'; import { useCurrentServer, useAlbumListStore, useSetAlbumFilters, useSetAlbumStore, + AlbumListFilter, } from '/@/renderer/store'; import { CardDisplayType } from '/@/renderer/types'; import { useMusicFolders } from '/@/renderer/features/shared'; import styled from 'styled-components'; import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters'; import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters'; +import { api } from '/@/renderer/api'; +import { controller } from '/@/renderer/api/controller'; +import { queryKeys } from '/@/renderer/api/query-keys'; const FILTERS = { jellyfin: [ @@ -72,7 +78,12 @@ const HeaderItems = styled.div` justify-content: space-between; `; -export const AlbumListHeader = () => { +interface AlbumListHeaderProps { + gridRef: MutableRefObject; +} + +export const AlbumListHeader = ({ gridRef }: AlbumListHeaderProps) => { + const queryClient = useQueryClient(); const server = useCurrentServer(); const setPage = useSetAlbumStore(); const setFilter = useSetAlbumFilters(); @@ -94,6 +105,47 @@ export const AlbumListHeader = () => { 200, ); + const fetch = useCallback( + async (skip: number, take: number, filters: AlbumListFilter) => { + const queryKey = queryKeys.albums.list(server?.id || '', { + limit: take, + startIndex: skip, + ...filters, + }); + + const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) => + controller.getAlbumList({ + query: { + limit: take, + startIndex: skip, + ...filters, + }, + server, + signal, + }), + ); + + return api.normalize.albumList(albums, server); + }, + [queryClient, server], + ); + + const handleFilterChange = useCallback( + async (filters: any) => { + gridRef.current?.scrollTo(0); + gridRef.current?.resetLoadMoreItemsCache(); + + // Refetching within the virtualized grid may be inconsistent due to it refetching + // using an outdated set of filters. To avoid this, we fetch using the updated filters + // and then set the grid's data here. + const data = await fetch(0, 200, filters); + + if (!data?.items) return; + gridRef.current?.setItemData(data.items); + }, + [gridRef, fetch], + ); + const handleSetSortBy = useCallback( (e: MouseEvent) => { if (!e.currentTarget?.value || !server?.type) return; @@ -102,32 +154,32 @@ export const AlbumListHeader = () => { (f) => f.value === e.currentTarget.value, )?.defaultOrder; - setFilter({ + const updatedFilters = setFilter({ sortBy: e.currentTarget.value as AlbumListSort, sortOrder: sortOrder || SortOrder.ASC, }); + + handleFilterChange(updatedFilters); }, - [server?.type, setFilter], + [handleFilterChange, server?.type, setFilter], ); const handleSetMusicFolder = useCallback( (e: MouseEvent) => { if (!e.currentTarget?.value) return; - setFilter({ - musicFolderId: e.currentTarget.value, - }); + const updatedFilters = setFilter({ musicFolderId: e.currentTarget.value }); + handleFilterChange(updatedFilters); }, - [setFilter], + [handleFilterChange, setFilter], ); const handleSetOrder = useCallback( (e: MouseEvent) => { if (!e.currentTarget?.value) return; - setFilter({ - sortOrder: e.currentTarget.value as SortOrder, - }); + const updatedFilters = setFilter({ sortOrder: e.currentTarget.value as SortOrder }); + handleFilterChange(updatedFilters); }, - [setFilter], + [handleFilterChange, setFilter], ); const handleSetViewType = useCallback( @@ -135,26 +187,11 @@ export const AlbumListHeader = () => { if (!e.currentTarget?.value) return; const type = e.currentTarget.value; if (type === CardDisplayType.CARD) { - setPage({ - list: { - ...page, - display: CardDisplayType.CARD, - }, - }); + setPage({ list: { ...page, display: CardDisplayType.CARD } }); } else if (type === CardDisplayType.POSTER) { - setPage({ - list: { - ...page, - display: CardDisplayType.POSTER, - }, - }); + setPage({ list: { ...page, display: CardDisplayType.POSTER } }); } else { - setPage({ - list: { - ...page, - display: CardDisplayType.TABLE, - }, - }); + setPage({ list: { ...page, display: CardDisplayType.TABLE } }); } }, [page, setPage], diff --git a/src/renderer/features/albums/routes/album-list-route.tsx b/src/renderer/features/albums/routes/album-list-route.tsx index 9b8e98bc..2d11057b 100644 --- a/src/renderer/features/albums/routes/album-list-route.tsx +++ b/src/renderer/features/albums/routes/album-list-route.tsx @@ -1,14 +1,17 @@ -import { VirtualGridContainer } from '/@/renderer/components'; +import { VirtualGridContainer, VirtualInfiniteGridRef } from '/@/renderer/components'; import { AnimatedPage } from '/@/renderer/features/shared'; import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header'; import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content'; +import { useRef } from 'react'; const AlbumListRoute = () => { + const gridRef = useRef(null); + return ( - - + + );