diff --git a/package.json b/package.json index 1f8d1635..732a1512 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,9 @@ "@sentry/react-native": "6.17.0", "@shopify/flash-list": "^2.0.3", "@tamagui/config": "^1.132.23", - "@tanstack/query-async-storage-persister": "^5.85.6", - "@tanstack/react-query": "^5.85.6", - "@tanstack/react-query-persist-client": "^5.85.6", + "@tanstack/query-async-storage-persister": "^5.85.9", + "@tanstack/react-query": "^5.85.9", + "@tanstack/react-query-persist-client": "^5.85.9", "@testing-library/react-native": "^13.2.3", "@typedigital/telemetrydeck-react": "^0.4.1", "axios": "^1.11.0", diff --git a/src/api/queries/album/utils/album.ts b/src/api/queries/album/utils/album.ts index 225f9292..28d82db3 100644 --- a/src/api/queries/album/utils/album.ts +++ b/src/api/queries/album/utils/album.ts @@ -32,7 +32,7 @@ export function fetchAlbums( parentId: library.musicLibraryId, includeItemTypes: [BaseItemKind.MusicAlbum], userId: user.id, - enableUserData: false, // This data is fetched lazily on component render + enableUserData: true, // This will populate the user data query later down the line sortBy, sortOrder, startIndex: page * ApiLimits.Library, diff --git a/src/api/queries/artist/utils/artist.ts b/src/api/queries/artist/utils/artist.ts index dd4b0e58..dfc78acc 100644 --- a/src/api/queries/artist/utils/artist.ts +++ b/src/api/queries/artist/utils/artist.ts @@ -31,7 +31,7 @@ export function fetchArtists( .getAlbumArtists({ parentId: library.musicLibraryId, userId: user.id, - enableUserData: false, // This data is fetched lazily on component render + enableUserData: true, // This will populate the User Data query later down the line sortBy: sortBy, sortOrder: sortOrder, startIndex: page * ApiLimits.Library, diff --git a/src/api/queries/favorites.ts b/src/api/queries/favorites.ts index 276f34e1..2b3d3b98 100644 --- a/src/api/queries/favorites.ts +++ b/src/api/queries/favorites.ts @@ -6,7 +6,6 @@ import { BaseItemKind, ItemSortBy, SortOrder, - UserItemDataDto, } from '@jellyfin/sdk/lib/generated-client/models' import { getItemsApi } from '@jellyfin/sdk/lib/utils/api' import { isUndefined } from 'lodash' @@ -178,33 +177,3 @@ export async function fetchFavoriteTracks( }) }) } - -/** - * Fetches the {@link UserItemDataDto} for a given {@link BaseItemDto} - * @param api The Jellyfin {@link Api} instance - * @param itemId The ID field of the {@link BaseItemDto} to fetch user data for - * @returns The {@link UserItemDataDto} for the given item - */ -export async function fetchUserData( - api: Api | undefined, - user: JellifyUser | undefined, - itemId: string, -): Promise { - return new Promise((resolve, reject) => { - if (isUndefined(api)) return reject('Client instance not set') - if (isUndefined(user)) return reject('User instance not set') - - getItemsApi(api) - .getItemUserData({ - itemId, - userId: user.id, - }) - .then((response) => { - return resolve(response.data) - }) - .catch((error) => { - console.error(error) - return reject(error) - }) - }) -} diff --git a/src/api/queries/frequents.ts b/src/api/queries/frequents.ts index a97ebba8..a9db73cb 100644 --- a/src/api/queries/frequents.ts +++ b/src/api/queries/frequents.ts @@ -35,6 +35,7 @@ export function fetchFrequentlyPlayed( startIndex: page * 100, sortBy: [ItemSortBy.PlayCount], sortOrder: [SortOrder.Descending], + enableUserData: true, }) .then(({ data }) => { if (data.Items) resolve(data.Items) diff --git a/src/api/queries/item.ts b/src/api/queries/item.ts index 6c9c8dd1..cf63fc62 100644 --- a/src/api/queries/item.ts +++ b/src/api/queries/item.ts @@ -29,6 +29,7 @@ export async function fetchItem(api: Api | undefined, itemId: string): Promise { if (response.data.Items && response.data.TotalRecordCount == 1) diff --git a/src/api/queries/media/index.ts b/src/api/queries/media/index.ts index 7af387be..3f05bb72 100644 --- a/src/api/queries/media/index.ts +++ b/src/api/queries/media/index.ts @@ -45,6 +45,7 @@ const useStreamedMediaInfo = (itemId: string | null | undefined) => { return useQuery({ queryKey: mediaInfoQueryKey({ api, user, deviceProfile, itemId }), queryFn: () => fetchMediaInfo(api, user, deviceProfile, itemId), + staleTime: Infinity, // Only refetch when the user's device profile changes }) } @@ -72,5 +73,6 @@ export const useDownloadedMediaInfo = (itemId: string | null | undefined) => { return useQuery({ queryKey: mediaInfoQueryKey({ api, user, deviceProfile, itemId }), queryFn: () => fetchMediaInfo(api, user, deviceProfile, itemId), + staleTime: Infinity, // Only refetch when the user's device profile changes }) } diff --git a/src/api/queries/recents.ts b/src/api/queries/recents.ts index e722bf29..015a7674 100644 --- a/src/api/queries/recents.ts +++ b/src/api/queries/recents.ts @@ -73,6 +73,7 @@ export async function fetchRecentlyPlayed( sortBy: [ItemSortBy.DatePlayed], sortOrder: [SortOrder.Descending], fields: [ItemFields.ParentId], + enableUserData: true, }) .then((response) => { console.debug('Received recently played items response') diff --git a/src/api/queries/tracks.ts b/src/api/queries/tracks.ts index af662906..83a86e67 100644 --- a/src/api/queries/tracks.ts +++ b/src/api/queries/tracks.ts @@ -30,6 +30,7 @@ export function fetchTracks( .getItems({ includeItemTypes: [BaseItemKind.Audio], parentId: library.musicLibraryId, + enableUserData: true, userId: user.id, recursive: true, isFavorite: isFavorite, diff --git a/src/api/queries/user-data/index.ts b/src/api/queries/user-data/index.ts new file mode 100644 index 00000000..ea2ba7b4 --- /dev/null +++ b/src/api/queries/user-data/index.ts @@ -0,0 +1,17 @@ +import { useJellifyContext } from '../../../providers' +import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto' +import { useQuery } from '@tanstack/react-query' +import fetchUserData from './utils' +import UserDataQueryKey from './keys' +import { ONE_MINUTE } from '@/src/constants/query-client' + +export const useIsFavorite = (item: BaseItemDto) => { + const { api, user } = useJellifyContext() + + return useQuery({ + queryKey: UserDataQueryKey(user!, item), + queryFn: () => fetchUserData(api, user, item.Id!), + select: (data) => typeof data === 'object' && data.IsFavorite, + enabled: !!api && !!user && !!item.Id, // Only run if we have the required data + }) +} diff --git a/src/api/queries/user-data/keys.ts b/src/api/queries/user-data/keys.ts new file mode 100644 index 00000000..f848acd6 --- /dev/null +++ b/src/api/queries/user-data/keys.ts @@ -0,0 +1,11 @@ +import { QueryKeys } from '../../../enums/query-keys' +import { JellifyUser } from '../../../types/JellifyUser' +import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client' + +const UserDataQueryKey = (user: JellifyUser, item: BaseItemDto) => [ + QueryKeys.UserData, + user.id, + item.Id, +] + +export default UserDataQueryKey diff --git a/src/api/queries/user-data/utils/index.ts b/src/api/queries/user-data/utils/index.ts new file mode 100644 index 00000000..917e229d --- /dev/null +++ b/src/api/queries/user-data/utils/index.ts @@ -0,0 +1,35 @@ +import { JellifyUser } from '@/src/types/JellifyUser' +import { Api } from '@jellyfin/sdk/lib/api' +import { UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models/user-item-data-dto' +import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api' +import { isUndefined } from 'lodash' + +/** + * Fetches the {@link UserItemDataDto} for a given {@link BaseItemDto} + * @param api The Jellyfin {@link Api} instance + * @param itemId The ID field of the {@link BaseItemDto} to fetch user data for + * @returns The {@link UserItemDataDto} for the given item + */ +export default async function fetchUserData( + api: Api | undefined, + user: JellifyUser | undefined, + itemId: string, +): Promise { + return new Promise((resolve, reject) => { + if (isUndefined(api)) return reject('Client instance not set') + if (isUndefined(user)) return reject('User instance not set') + + getItemsApi(api) + .getItemUserData({ + itemId, + userId: user.id, + }) + .then((response) => { + return resolve(response.data) + }) + .catch((error) => { + console.error(error) + return reject(error) + }) + }) +} diff --git a/src/components/Albums/component.tsx b/src/components/Albums/component.tsx index 0159543b..4cd24f3f 100644 --- a/src/components/Albums/component.tsx +++ b/src/components/Albums/component.tsx @@ -2,16 +2,13 @@ import { ActivityIndicator, RefreshControl } from 'react-native' import { getToken, Separator, XStack, YStack } from 'tamagui' import React, { RefObject, useEffect, useRef } from 'react' import { Text } from '../Global/helpers/text' -import { FlashList, FlashListRef, ViewToken } from '@shopify/flash-list' +import { FlashList, FlashListRef } from '@shopify/flash-list' import { UseInfiniteQueryResult } from '@tanstack/react-query' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models' import ItemRow from '../Global/components/item-row' import { useNavigation } from '@react-navigation/native' import LibraryStackParamList from '../../screens/Library/types' import { NativeStackNavigationProp } from '@react-navigation/native-stack' -import { warmItemContext } from '../../hooks/use-item-context' -import { useJellifyContext } from '../../providers' -import useStreamingDeviceProfile from '../../stores/device-profile' import AZScroller, { useAlphabetSelector } from '../Global/components/alphabetical-selector' import { isString } from 'lodash' @@ -28,23 +25,10 @@ export default function Albums({ }: AlbumsProps): React.JSX.Element { const navigation = useNavigation>() - const { api, user } = useJellifyContext() - - const deviceProfile = useStreamingDeviceProfile() - const sectionListRef = useRef>(null) const pendingLetterRef = useRef(null) - const onViewableItemsChangedRef = useRef( - ({ viewableItems }: { viewableItems: ViewToken[] }) => { - viewableItems.forEach(({ isViewable, item }) => { - if (isViewable && typeof item === 'object') - warmItemContext(api, user, item, deviceProfile) - }) - }, - ) - // Memoize expensive stickyHeaderIndices calculation to prevent unnecessary re-computations const stickyHeaderIndices = React.useMemo(() => { if (!showAlphabeticalSelector || !albumsInfiniteQuery.data) return [] @@ -155,7 +139,6 @@ export default function Albums({ } stickyHeaderIndices={stickyHeaderIndices} removeClippedSubviews - onViewableItemsChanged={onViewableItemsChangedRef.current} /> {showAlphabeticalSelector && albumPageParams && ( diff --git a/src/components/Artist/albums.tsx b/src/components/Artist/albums.tsx index 7e8fbfd3..1f4c8018 100644 --- a/src/components/Artist/albums.tsx +++ b/src/components/Artist/albums.tsx @@ -1,26 +1,18 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useState } from 'react' import { ItemCard } from '../Global/components/item-card' import { ArtistAlbumsProps, ArtistEpsProps, ArtistFeaturedOnProps } from './types' import { Text } from '../Global/helpers/text' import { useArtistContext } from '../../providers/Artist' import { convertRunTimeTicksToSeconds } from '../../utils/runtimeticks' import Animated, { useAnimatedScrollHandler } from 'react-native-reanimated' -import { ActivityIndicator, ViewToken } from 'react-native' +import { ActivityIndicator } from 'react-native' import { useSafeAreaFrame } from 'react-native-safe-area-context' import { getToken } from 'tamagui' import navigationRef from '../../../navigation' -import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto' -import { warmItemContext } from '../../hooks/use-item-context' -import { useJellifyContext } from '../../providers' -import useStreamingDeviceProfile from '../../stores/device-profile' export default function Albums({ route, navigation, }: ArtistAlbumsProps | ArtistEpsProps | ArtistFeaturedOnProps): React.JSX.Element { - const { api, user } = useJellifyContext() - - const deviceProfile = useStreamingDeviceProfile() - const { width } = useSafeAreaFrame() const { albums, fetchingAlbums, featuredOn, scroll } = useArtistContext() const scrollHandler = useAnimatedScrollHandler({ @@ -30,14 +22,6 @@ export default function Albums({ }, }) - const onViewableItemsChangedRef = useRef( - ({ viewableItems }: { viewableItems: ViewToken[] }) => { - viewableItems.forEach(({ isViewable, item }) => { - if (isViewable) warmItemContext(api, user, item, deviceProfile) - }) - }, - ) - const [columns, setColumns] = useState(Math.floor(width / getToken('$20'))) useEffect(() => { @@ -113,7 +97,6 @@ export default function Albums({ ) } removeClippedSubviews - onViewableItemsChanged={onViewableItemsChangedRef.current} /> ) } diff --git a/src/components/Artists/component.tsx b/src/components/Artists/component.tsx index bbef1af8..c8e76117 100644 --- a/src/components/Artists/component.tsx +++ b/src/components/Artists/component.tsx @@ -5,16 +5,13 @@ import { RefreshControl } from 'react-native' import ItemRow from '../Global/components/item-row' import { useLibrarySortAndFilterContext } from '../../providers/Library' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto' -import { FlashList, FlashListRef, ViewToken } from '@shopify/flash-list' +import { FlashList, FlashListRef } from '@shopify/flash-list' import AZScroller, { useAlphabetSelector } from '../Global/components/alphabetical-selector' -import { UseInfiniteQueryResult, useMutation } from '@tanstack/react-query' +import { UseInfiniteQueryResult } from '@tanstack/react-query' import { isString } from 'lodash' import { useNavigation } from '@react-navigation/native' import { NativeStackNavigationProp } from '@react-navigation/native-stack' import LibraryStackParamList from '../../screens/Library/types' -import { warmItemContext } from '../../hooks/use-item-context' -import { useJellifyContext } from '../../providers' -import useStreamingDeviceProfile from '../../stores/device-profile' export interface ArtistsProps { artistsInfiniteQuery: UseInfiniteQueryResult< @@ -39,10 +36,6 @@ export default function Artists({ }: ArtistsProps): React.JSX.Element { const theme = useTheme() - const { api, user } = useJellifyContext() - - const deviceProfile = useStreamingDeviceProfile() - const { isFavorites } = useLibrarySortAndFilterContext() const navigation = useNavigation>() @@ -52,15 +45,6 @@ export default function Artists({ const pendingLetterRef = useRef(null) - const onViewableItemsChangedRef = useRef( - ({ viewableItems }: { viewableItems: ViewToken[] }) => { - viewableItems.forEach(({ isViewable, item }) => { - if (isViewable && typeof item === 'object') - warmItemContext(api, user, item, deviceProfile) - }) - }, - ) - const { mutate: alphabetSelectorMutate, isPending: isAlphabetSelectorPending } = useAlphabetSelector((letter) => (pendingLetterRef.current = letter.toUpperCase())) @@ -177,7 +161,6 @@ export default function Artists({ }} // onEndReachedThreshold default is 0.5 removeClippedSubviews - onViewableItemsChanged={onViewableItemsChangedRef.current} /> {showAlphabeticalSelector && artistPageParams && ( diff --git a/src/components/Global/components/favorite-button.tsx b/src/components/Global/components/favorite-button.tsx index 1001bd2a..a15d4cd8 100644 --- a/src/components/Global/components/favorite-button.tsx +++ b/src/components/Global/components/favorite-button.tsx @@ -1,44 +1,22 @@ import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models' import React from 'react' import Icon from './icon' -import { useQuery } from '@tanstack/react-query' import { isUndefined } from 'lodash' -import { Spinner } from 'tamagui' -import { QueryKeys } from '../../../enums/query-keys' -import { fetchUserData } from '../../../api/queries/favorites' import { useJellifyUserDataContext } from '../../../providers/UserData' -import { useJellifyContext } from '../../../providers' import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' -import { ONE_HOUR } from '../../../constants/query-client' +import { useIsFavorite } from '../../../api/queries/user-data' -interface SetFavoriteMutation { - item: BaseItemDto -} - -export default function FavoriteButton({ - item, - onToggle, -}: { +interface FavoriteButtonProps { item: BaseItemDto onToggle?: () => void -}): React.JSX.Element { - const { api, user } = useJellifyContext() +} + +export default function FavoriteButton({ item, onToggle }: FavoriteButtonProps): React.JSX.Element { const { toggleFavorite } = useJellifyUserDataContext() - const { - data: isFavorite, - isFetching, - refetch, - } = useQuery({ - queryKey: [QueryKeys.UserData, item.Id], - queryFn: () => fetchUserData(api, user, item.Id!), - select: (data) => typeof data === 'object' && data.IsFavorite, - staleTime: ONE_HOUR, - }) + const { data: isFavorite } = useIsFavorite(item) - return isFetching ? ( - - ) : isFavorite ? ( + return isFavorite ? ( fetchUserData(api, user, item.Id!), - select: (data) => typeof data === 'object' && data.IsFavorite, - staleTime: ONE_HOUR, - }) + const { data: isFavorite, refetch } = useIsFavorite(item) return isFavorite ? ( fetchUserData(api, user, item.Id!), - select: (data) => typeof data === 'object' && data.IsFavorite, - enabled: !!api && !!user && !!item.Id, // Only run if we have the required data - }) + const { data: isFavorite } = useIsFavorite(item) return isFavorite ? ( diff --git a/src/components/Global/components/horizontal-list.tsx b/src/components/Global/components/horizontal-list.tsx index d60607bb..ad0cc101 100644 --- a/src/components/Global/components/horizontal-list.tsx +++ b/src/components/Global/components/horizontal-list.tsx @@ -1,9 +1,6 @@ -import useStreamingDeviceProfile from '../../../stores/device-profile' -import { warmItemContext } from '../../../hooks/use-item-context' -import { useJellifyContext } from '../../../providers' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto' -import { FlashList, FlashListProps, ViewToken } from '@shopify/flash-list' -import React, { useRef } from 'react' +import { FlashList, FlashListProps } from '@shopify/flash-list' +import React from 'react' interface HorizontalCardListProps extends FlashListProps {} @@ -16,23 +13,10 @@ interface HorizontalCardListProps extends FlashListProps {} export default function HorizontalCardList({ ...props }: HorizontalCardListProps): React.JSX.Element { - const { api, user } = useJellifyContext() - - const deviceProfile = useStreamingDeviceProfile() - - const onViewableItemsChangedRef = useRef( - ({ viewableItems }: { viewableItems: ViewToken[] }) => { - viewableItems - .filter(({ isViewable }) => isViewable) - .forEach(({ item }) => warmItemContext(api, user, item, deviceProfile)) - }, - ) - return ( { switch (item.Type) { case 'Audio': { @@ -82,6 +85,7 @@ export default function ItemRow({ alignContent='center' minHeight={'$7'} width={'100%'} + onPressIn={warmContext} onLongPress={() => { navigationRef.navigate('Context', { item, diff --git a/src/components/Global/components/track.tsx b/src/components/Global/components/track.tsx index dcd786e5..eca98df2 100644 --- a/src/components/Global/components/track.tsx +++ b/src/components/Global/components/track.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback } from 'react' +import React, { useMemo, useCallback, useEffect } from 'react' import { getToken, Theme, useTheme, XStack, YStack } from 'tamagui' import { Text } from '../helpers/text' import { RunTimeTicks } from '../helpers/time-codes' @@ -69,7 +69,7 @@ export default function Track({ const offlineAudio = useDownloadedTrack(track.Id) - useItemContext(track) + const warmContext = useItemContext(track) // Memoize expensive computations const isPlaying = useMemo( @@ -159,6 +159,10 @@ export default function Track({ [showArtwork, track.Artists], ) + useEffect(() => { + warmContext() + }, [track]) + return ( - + {isFavorites ? 'Favorites' : 'All'} diff --git a/src/components/Playlists/component.tsx b/src/components/Playlists/component.tsx index 753a9f86..8b081427 100644 --- a/src/components/Playlists/component.tsx +++ b/src/components/Playlists/component.tsx @@ -1,16 +1,12 @@ import { RefreshControl } from 'react-native-gesture-handler' import { Separator } from 'tamagui' -import { FlashList, ViewToken } from '@shopify/flash-list' +import { FlashList } from '@shopify/flash-list' import ItemRow from '../Global/components/item-row' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models' import { FetchNextPageOptions } from '@tanstack/react-query' import { useNavigation } from '@react-navigation/native' import { BaseStackParamList } from '@/src/screens/types' import { NativeStackNavigationProp } from '@react-navigation/native-stack' -import { useRef } from 'react' -import { warmItemContext } from '../../hooks/use-item-context' -import { useJellifyContext } from '../../providers' -import useStreamingDeviceProfile from '../../stores/device-profile' export interface PlaylistsProps { canEdit?: boolean | undefined @@ -32,18 +28,6 @@ export default function Playlists({ }: PlaylistsProps): React.JSX.Element { const navigation = useNavigation>() - const { api, user } = useJellifyContext() - - const deviceProfile = useStreamingDeviceProfile() - - const onViewableItemsChangedRef = useRef( - ({ viewableItems }: { viewableItems: ViewToken[] }) => { - viewableItems.forEach(({ isViewable, item }) => { - if (isViewable) warmItemContext(api, user, item, deviceProfile) - }) - }, - ) - return ( ) } diff --git a/src/components/Tracks/component.tsx b/src/components/Tracks/component.tsx index df499cb2..734b2e03 100644 --- a/src/components/Tracks/component.tsx +++ b/src/components/Tracks/component.tsx @@ -1,17 +1,15 @@ -import React, { useRef } from 'react' +import React from 'react' import Track from '../Global/components/track' import { getTokens, Separator } from 'tamagui' import { BaseItemDto, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models' import { Queue } from '../../player/types/queue-item' import { queryClient } from '../../constants/query-client' -import { QueryKeys } from '../../enums/query-keys' -import { FlashList, ViewToken } from '@shopify/flash-list' +import { FlashList } from '@shopify/flash-list' import { NativeStackNavigationProp } from '@react-navigation/native-stack' import { BaseStackParamList } from '../../screens/types' -import { warmItemContext } from '../../hooks/use-item-context' -import { useJellifyContext } from '../../providers' -import useStreamingDeviceProfile from '../../stores/device-profile' import { useAllDownloadedTracks } from '../../api/queries/download' +import UserDataQueryKey from '../../api/queries/user-data/keys' +import { useJellifyContext } from '../../providers' interface TracksProps { tracks: (string | number | BaseItemDto)[] | undefined @@ -32,9 +30,8 @@ export default function Tracks({ filterDownloaded, filterFavorites, }: TracksProps): React.JSX.Element { - const { api, user } = useJellifyContext() + const { user } = useJellifyContext() - const deviceProfile = useStreamingDeviceProfile() const { data: downloadedTracks } = useAllDownloadedTracks() // Memoize the expensive tracks processing to prevent memory leaks @@ -47,10 +44,9 @@ export default function Tracks({ if (filterFavorites) { return ( ( - queryClient.getQueryData([ - QueryKeys.UserData, - downloadedTrack.Id, - ]) as UserItemDataDto | undefined + queryClient.getQueryData( + UserDataQueryKey(user!, downloadedTrack), + ) as UserItemDataDto | undefined )?.IsFavorite ?? false ) } @@ -79,14 +75,6 @@ export default function Tracks({ [tracksToDisplay, queue], ) - const onViewableItemsChangedRef = useRef( - ({ viewableItems }: { viewableItems: ViewToken[] }) => { - viewableItems.forEach(({ isViewable, item }) => { - if (isViewable) warmItemContext(api, user, item, deviceProfile) - }) - }, - ) - return ( void { const { api, user } = useJellifyContext() const streamingDeviceProfile = useStreamingDeviceProfile() @@ -20,7 +21,7 @@ export default function useItemContext(item: BaseItemDto): void { const prefetchedContext = useRef>(new Set()) - useEffect(() => { + return useCallback(() => { const effectSig = `${item.Id}-${item.Type}` // If we've already warmed the cache for this item, return @@ -33,7 +34,7 @@ export default function useItemContext(item: BaseItemDto): void { }, [api, user, streamingDeviceProfile]) } -export function warmItemContext( +function warmItemContext( api: Api | undefined, user: JellifyUser | undefined, item: BaseItemDto, @@ -72,12 +73,11 @@ export function warmItemContext( }), }) - const userDataQueryKey = [QueryKeys.UserData, Id] - if (queryClient.getQueryState(userDataQueryKey)?.status !== 'success') { - if (UserData) queryClient.setQueryData([QueryKeys.UserData, Id], UserData) + if (queryClient.getQueryState(UserDataQueryKey(user!, item))?.status !== 'success') { + if (UserData) queryClient.setQueryData(UserDataQueryKey(user!, item), UserData) else queryClient.ensureQueryData({ - queryKey: [], + queryKey: UserDataQueryKey(user!, item), queryFn: () => fetchUserData(api, user, Id), }) } diff --git a/src/providers/UserData/index.tsx b/src/providers/UserData/index.tsx index e4f78387..00f0f4a5 100644 --- a/src/providers/UserData/index.tsx +++ b/src/providers/UserData/index.tsx @@ -8,6 +8,7 @@ import { QueryKeys } from '../../enums/query-keys' import Toast from 'react-native-toast-message' import { useJellifyContext } from '..' import useHapticFeedback from '../../hooks/use-haptic-feedback' +import UserDataQueryKey from '../../api/queries/user-data/keys' interface SetFavoriteMutation { item: BaseItemDto @@ -19,7 +20,7 @@ interface JellifyUserDataContext { } const JellifyUserDataContextInitializer = () => { - const { api } = useJellifyContext() + const { api, user } = useJellifyContext() const trigger = useHapticFeedback() @@ -45,7 +46,7 @@ const JellifyUserDataContextInitializer = () => { if (onToggle) onToggle() // Force refresh of track user data - queryClient.invalidateQueries({ queryKey: [QueryKeys.UserData, item.Id] }) + queryClient.invalidateQueries({ queryKey: UserDataQueryKey(user!, item) }) }, }) @@ -70,7 +71,7 @@ const JellifyUserDataContextInitializer = () => { if (onToggle) onToggle() // Force refresh of track user data - queryClient.invalidateQueries({ queryKey: [QueryKeys.UserData, item.Id] }) + queryClient.invalidateQueries({ queryKey: UserDataQueryKey(user!, item) }) }, }) diff --git a/yarn.lock b/yarn.lock index 41e6df85..cb82b54c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3402,38 +3402,38 @@ resolved "https://registry.yarnpkg.com/@tamagui/z-index-stack/-/z-index-stack-1.132.23.tgz#a74f06f3b6a6191951f396105f39a10aec0144aa" integrity sha512-djbRW7FWzuc9bCIVXG00pVa6McM8/H8R4JOL+szxSy1iAo0P2k0OzWfBb+ZbbjTye068fBPGIniq4X7+3huS1Q== -"@tanstack/query-async-storage-persister@^5.85.6": - version "5.85.6" - resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.85.6.tgz#a215d1a78fe23efab45b6f31d942cfe4515c5c36" - integrity sha512-f2C0tMVEo6oFdcNE1xOYGJ5KB02cIEDPjbWuqEivJfrlWDqsPlnHnfxTYkuMcddTHTClDf/sqtjuB95zggeEsQ== +"@tanstack/query-async-storage-persister@^5.85.9": + version "5.85.9" + resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.85.9.tgz#9f80f4f612a35b67cb4bee4238f1c24adb29515d" + integrity sha512-6+HevrTPQ8TbJ3iMPY9NckQ4eLTIHgoZZflQVIKKcpb7Y08OQ15kw8skm7CCkTZClsTz1+JNr9heuMCBluO+rw== dependencies: - "@tanstack/query-persist-client-core" "5.85.6" + "@tanstack/query-persist-client-core" "5.85.9" -"@tanstack/query-core@5.85.6": - version "5.85.6" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.85.6.tgz#2af90f3c56c38fd2194e0ed1996122373c4bfca5" - integrity sha512-hCj0TktzdCv2bCepIdfwqVwUVWb+GSHm1Jnn8w+40lfhQ3m7lCO7ADRUJy+2unxQ/nzjh2ipC6ye69NDW3l73g== +"@tanstack/query-core@5.85.9": + version "5.85.9" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.85.9.tgz#69e800f4d1c42ca4a110469229d8ecca409d477a" + integrity sha512-5fxb9vwyftYE6KFLhhhDyLr8NO75+Wpu7pmTo+TkwKmMX2oxZDoLwcqGP8ItKSpUMwk3urWgQDZfyWr5Jm9LsQ== -"@tanstack/query-persist-client-core@5.85.6": - version "5.85.6" - resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.85.6.tgz#92f9d3ac83f51b0bd452fe3b65047f4196c3f56a" - integrity sha512-wUdoEurIC0YCNZzR020Xcg3OsJeF4SXmEPqlNwZ6EaGKgWeNjU17hVdK+X4ZeirUm+h0muiEQx+aIQU1lk7roQ== +"@tanstack/query-persist-client-core@5.85.9": + version "5.85.9" + resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.85.9.tgz#2d7e89d37195a307d898d850652c1f4fca77158d" + integrity sha512-er7HfMjn6TQanWG5nudjRNZbo7ahf7IIdWN5kU7L2qRZ2kcw89TQZAZ74GIQsumOXZD7sUcHG2dylveFZNxlZA== dependencies: - "@tanstack/query-core" "5.85.6" + "@tanstack/query-core" "5.85.9" -"@tanstack/react-query-persist-client@^5.85.6": - version "5.85.6" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.85.6.tgz#40c59526f55dc4ee5ac8ddb5bc777e27e2256627" - integrity sha512-zLUfm8JlI6/s0AqvX5l5CcazdHwj5gwcv0mWYOaJJvADyFzl2wwQKqB/H4nYSeygUtrepBgPwVQKNqH9ZwlZpQ== +"@tanstack/react-query-persist-client@^5.85.9": + version "5.85.9" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.85.9.tgz#5e01d5d0744098fa7d0feefc397068092fcdfcb5" + integrity sha512-h75Xyt/XDtk1oRmli5znQRXclT/iZpseTK7ScqEjaiZmPSREgg2mJb1blo2SB1swSidDNsCtnENLNH43PP0/9w== dependencies: - "@tanstack/query-persist-client-core" "5.85.6" + "@tanstack/query-persist-client-core" "5.85.9" -"@tanstack/react-query@^5.85.6": - version "5.85.6" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.85.6.tgz#0885cd9e02f8a5aa228f6b5dc2122d22ba597d68" - integrity sha512-VUAag4ERjh+qlmg0wNivQIVCZUrYndqYu3/wPCVZd4r0E+1IqotbeyGTc+ICroL/PqbpSaGZg02zSWYfcvxbdA== +"@tanstack/react-query@^5.85.9": + version "5.85.9" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.85.9.tgz#d27315b3f327af31237d513e505f3e6a2cad5739" + integrity sha512-2T5zgSpcOZXGkH/UObIbIkGmUPQqZqn7esVQFXLOze622h4spgWf5jmvrqAo9dnI13/hyMcNsF1jsoDcb59nJQ== dependencies: - "@tanstack/query-core" "5.85.6" + "@tanstack/query-core" "5.85.9" "@telemetrydeck/sdk@^2.0.4": version "2.0.4"