refactor: Implement optimistic updates for adding tracks to playlists, remove usePlaylistTracks, and display playlist.ChildCount.

This commit is contained in:
skalthoff
2025-12-08 00:42:10 -08:00
parent c6a2320e36
commit 8532439828

View File

@@ -23,7 +23,7 @@ import TextTicker from 'react-native-text-ticker'
import { TextTickerConfig } from '../Player/component.config' import { TextTickerConfig } from '../Player/component.config'
import { getItemName } from '../../utils/text' import { getItemName } from '../../utils/text'
import useHapticFeedback from '../../hooks/use-haptic-feedback' import useHapticFeedback from '../../hooks/use-haptic-feedback'
import { usePlaylistTracks, useUserPlaylists } from '../../api/queries/playlist' import { useUserPlaylists } from '../../api/queries/playlist'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useApi, useJellifyUser } from '../../stores' import { useApi, useJellifyUser } from '../../stores'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
@@ -105,11 +105,7 @@ function AddToPlaylistRow({
const trigger = useHapticFeedback() const trigger = useHapticFeedback()
const { const [isInPlaylist, setIsInPlaylist] = useState<boolean>(false)
data: playlistTracks,
isPending: fetchingPlaylistTracks,
refetch: refetchPlaylistTracks,
} = usePlaylistTracks(playlist)
const useAddToPlaylist = useMutation({ const useAddToPlaylist = useMutation({
mutationFn: ({ mutationFn: ({
@@ -124,14 +120,17 @@ function AddToPlaylistRow({
return addToPlaylist(api, user, track!, playlist) return addToPlaylist(api, user, track!, playlist)
}, },
onSuccess: (data, { playlist }) => { onMutate: () => {
trigger('notificationSuccess') // Optimistic update - show success state immediately
setIsInPlaylist(true) setIsInPlaylist(true)
},
refetchPlaylistTracks() onSuccess: () => {
trigger('notificationSuccess')
}, },
onError: () => { onError: () => {
// Rollback on error
setIsInPlaylist(false)
Toast.show({ Toast.show({
text1: 'Unable to add', text1: 'Unable to add',
type: 'error', type: 'error',
@@ -141,22 +140,16 @@ function AddToPlaylistRow({
}, },
}) })
const [isInPlaylist, setIsInPlaylist] = useState<boolean>(
tracks.filter((track) =>
playlistTracks?.map((playlistTrack) => playlistTrack.Id).includes(track.Id),
).length > 0,
)
return ( return (
<YGroup.Item key={playlist.Id!}> <YGroup.Item key={playlist.Id!}>
<ListItem <ListItem
animation={'quick'} animation={'quick'}
disabled={isInPlaylist} disabled={isInPlaylist || useAddToPlaylist.isPending}
hoverTheme hoverTheme
opacity={isInPlaylist ? 0.5 : 1} opacity={isInPlaylist ? 0.5 : 1}
pressStyle={{ opacity: 0.6 }} pressStyle={{ opacity: 0.6 }}
onPress={() => { onPress={() => {
if (!isInPlaylist) { if (!isInPlaylist && !useAddToPlaylist.isPending) {
useAddToPlaylist.mutate({ useAddToPlaylist.mutate({
track: undefined, track: undefined,
tracks, tracks,
@@ -172,14 +165,14 @@ function AddToPlaylistRow({
<Text bold>{playlist.Name ?? 'Untitled Playlist'}</Text> <Text bold>{playlist.Name ?? 'Untitled Playlist'}</Text>
<Text color={getTokens().color.amethyst.val}>{`${ <Text color={getTokens().color.amethyst.val}>{`${
playlistTracks?.length ?? 0 playlist.ChildCount ?? 0
} tracks`}</Text> } tracks`}</Text>
</YStack> </YStack>
<Animated.View entering={FadeIn} exiting={FadeOut}> <Animated.View entering={FadeIn} exiting={FadeOut}>
{isInPlaylist ? ( {isInPlaylist ? (
<Icon flex={1} name='check-circle-outline' color={'$success'} /> <Icon flex={1} name='check-circle-outline' color={'$success'} />
) : fetchingPlaylistTracks ? ( ) : useAddToPlaylist.isPending ? (
<Spinner color={'$primary'} /> <Spinner color={'$primary'} />
) : ( ) : (
<Spacer flex={1} /> <Spacer flex={1} />