import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; /* Starred themes, per account. With 200+ installed, "which of these did I like" is a question the list cannot answer by itself, and the answer belongs to the account rather than to the browser — the same taste follows you to the phone. Optimistic, and that is not a nicety here: the star sits in a dropdown you are about to close, so a round trip before the icon fills means the confirmation arrives after the thing it confirms has gone. */ const KEY = ['themes', 'favorites']; export function useThemeFavorites() { const client = useClient(); const queryClient = useQueryClient(); const query = useQuery<{ favorites: string[] }>({ queryKey: KEY, queryFn: () => client.get<{ favorites: string[] }>('/theme-favorites'), // Taste does not change while you look at it, and the mutation below keeps the cache honest. staleTime: 5 * 60_000, refetchOnWindowFocus: false, retry: false, }); const toggle = useMutation({ mutationFn: ({ themeId, favorite }: { themeId: string; favorite: boolean }) => client.put<{ themeId: string; favorite: boolean }>(`/theme-favorites/${themeId}`, { favorite }), onMutate: async ({ themeId, favorite }) => { await queryClient.cancelQueries({ queryKey: KEY }); const previous = queryClient.getQueryData<{ favorites: string[] }>(KEY); const current = previous?.favorites ?? []; queryClient.setQueryData(KEY, { favorites: favorite ? [...new Set([...current, themeId])] : current.filter((id) => id !== themeId), }); return { previous }; }, // Put it back on failure. A star that stayed filled after the write was refused is worse than one that // never filled: it reports a preference the server does not hold. onError: (_err, _vars, context) => { if (context?.previous) queryClient.setQueryData(KEY, context.previous); }, }); const favorites = new Set(query.data?.favorites ?? []); return { favorites, isFavorite: (themeId: string) => favorites.has(themeId), toggle: (themeId: string) => toggle.mutate({ themeId, favorite: !favorites.has(themeId) }), }; }