From 65f6e83f11c82b0ec762e11f3392a80e614ee69a Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 1 Feb 2026 12:29:54 +0000 Subject: refactor: migrate trpc to the new react query integration mode (#2438) * refactor: migrate trpc to the new react query integration mode * more fixes * more migrations * upgrade trpc client --- packages/shared-react/hooks/assets.ts | 99 ++++++---- packages/shared-react/hooks/bookmarks.ts | 250 +++++++++++++++--------- packages/shared-react/hooks/highlights.ts | 93 +++++---- packages/shared-react/hooks/lists.ts | 196 +++++++++++-------- packages/shared-react/hooks/reader-settings.tsx | 49 ++--- packages/shared-react/hooks/rules.ts | 69 ++++--- packages/shared-react/hooks/tags.ts | 203 +++++++++++-------- packages/shared-react/hooks/users.ts | 56 +++--- 8 files changed, 615 insertions(+), 400 deletions(-) (limited to 'packages/shared-react/hooks') diff --git a/packages/shared-react/hooks/assets.ts b/packages/shared-react/hooks/assets.ts index 5367e97c..8be21304 100644 --- a/packages/shared-react/hooks/assets.ts +++ b/packages/shared-react/hooks/assets.ts @@ -1,49 +1,74 @@ -import { api } from "../trpc"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType; export function useAttachBookmarkAsset( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.assets.attachAsset.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.assets.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.assets.attachAsset.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.assets.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useReplaceBookmarkAsset( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.assets.replaceAsset.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.assets.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.assets.replaceAsset.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.assets.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDetachBookmarkAsset( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.assets.detachAsset.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.assets.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.assets.detachAsset.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.assets.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/bookmarks.ts b/packages/shared-react/hooks/bookmarks.ts index aea2d185..6ff75d7e 100644 --- a/packages/shared-react/hooks/bookmarks.ts +++ b/packages/shared-react/hooks/bookmarks.ts @@ -1,132 +1,196 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + import { getBookmarkRefreshInterval } from "@karakeep/shared/utils/bookmarkUtils"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; import { useBookmarkGridContext } from "./bookmark-grid-context"; import { useAddBookmarkToList } from "./lists"; +type TRPCApi = ReturnType; + export function useAutoRefreshingBookmarkQuery( - input: Parameters[0], + input: Parameters[0], ) { - return api.bookmarks.getBookmark.useQuery(input, { - refetchInterval: (query) => { - const data = query.state.data; - if (!data) { - return false; - } - return getBookmarkRefreshInterval(data); - }, - }); + const api = useTRPC(); + return useQuery( + api.bookmarks.getBookmark.queryOptions(input, { + refetchInterval: (query) => { + const data = query.state.data; + if (!data) { + return false; + } + return getBookmarkRefreshInterval(data); + }, + }), + ); } export function useCreateBookmark( - ...opts: Parameters + opts?: Parameters< + TRPCApi["bookmarks"]["createBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.createBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.createBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useCreateBookmarkWithPostHook( - ...opts: Parameters + opts?: Parameters< + TRPCApi["bookmarks"]["createBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); + const api = useTRPC(); + const queryClient = useQueryClient(); const postCreationCB = useBookmarkPostCreationHook(); - return api.bookmarks.createBookmark.useMutation({ - ...opts[0], - onSuccess: async (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - await postCreationCB(res.id); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + return useMutation( + api.bookmarks.createBookmark.mutationOptions({ + ...opts, + onSuccess: async (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + await postCreationCB(res.id); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteBookmark( - ...opts: Parameters + opts?: Parameters< + TRPCApi["bookmarks"]["deleteBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.deleteBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.deleteBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateBookmark( - ...opts: Parameters + opts?: Parameters< + TRPCApi["bookmarks"]["updateBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.updateBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.updateBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useSummarizeBookmark( - ...opts: Parameters + opts?: Parameters< + TRPCApi["bookmarks"]["summarizeBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.summarizeBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.summarizeBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useRecrawlBookmark( - ...opts: Parameters + opts?: Parameters< + TRPCApi["bookmarks"]["recrawlBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.recrawlBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.recrawlBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateBookmarkTags( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.updateTags.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - - [...res.attached, ...res.detached].forEach((id) => { - apiUtils.tags.get.invalidate({ tagId: id }); - apiUtils.bookmarks.getBookmarks.invalidate({ tagId: id }); - }); - apiUtils.tags.list.invalidate(); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.updateTags.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + + [...res.attached, ...res.detached].forEach((id) => { + queryClient.invalidateQueries( + api.tags.get.queryFilter({ tagId: id }), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ tagId: id }), + ); + }); + queryClient.invalidateQueries(api.tags.list.pathFilter()); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } /** diff --git a/packages/shared-react/hooks/highlights.ts b/packages/shared-react/hooks/highlights.ts index e642f878..3f6a6e01 100644 --- a/packages/shared-react/hooks/highlights.ts +++ b/packages/shared-react/hooks/highlights.ts @@ -1,49 +1,68 @@ -import { api } from "../trpc"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType; export function useCreateHighlight( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.highlights.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.highlights.getForBookmark.invalidate({ - bookmarkId: req.bookmarkId, - }); - apiUtils.highlights.getAll.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.highlights.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.highlights.getForBookmark.queryFilter({ + bookmarkId: req.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.highlights.getAll.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateHighlight( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.highlights.update.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.highlights.getForBookmark.invalidate({ - bookmarkId: res.bookmarkId, - }); - apiUtils.highlights.getAll.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.highlights.update.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.highlights.getForBookmark.queryFilter({ + bookmarkId: res.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.highlights.getAll.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteHighlight( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.highlights.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.highlights.getForBookmark.invalidate({ - bookmarkId: res.bookmarkId, - }); - apiUtils.highlights.getAll.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.highlights.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.highlights.getForBookmark.queryFilter({ + bookmarkId: res.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.highlights.getAll.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/lists.ts b/packages/shared-react/hooks/lists.ts index d269efe3..231e43a7 100644 --- a/packages/shared-react/hooks/lists.ts +++ b/packages/shared-react/hooks/lists.ts @@ -1,113 +1,155 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + import { ZBookmarkList } from "@karakeep/shared/types/lists"; import { listsToTree, ZBookmarkListRoot, } from "@karakeep/shared/utils/listUtils"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType; export function useCreateBookmarkList( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.lists.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useEditBookmarkList( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.lists.edit.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - apiUtils.lists.get.invalidate({ listId: req.listId }); - if (res.type === "smart") { - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); - } - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.edit.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + queryClient.invalidateQueries( + api.lists.get.queryFilter({ listId: req.listId }), + ); + if (res.type === "smart") { + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.listId }), + ); + } + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useMergeLists( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.lists.merge.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.targetId }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.merge.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.targetId }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useAddBookmarkToList( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.lists.addToList.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); - apiUtils.lists.getListsOfBookmark.invalidate({ - bookmarkId: req.bookmarkId, - }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.addToList.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.listId }), + ); + queryClient.invalidateQueries( + api.lists.getListsOfBookmark.queryFilter({ + bookmarkId: req.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useRemoveBookmarkFromList( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.lists.removeFromList.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); - apiUtils.lists.getListsOfBookmark.invalidate({ - bookmarkId: req.bookmarkId, - }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.removeFromList.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.listId }), + ); + queryClient.invalidateQueries( + api.lists.getListsOfBookmark.queryFilter({ + bookmarkId: req.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteBookmarkList( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.lists.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - apiUtils.lists.get.invalidate({ listId: req.listId }); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + queryClient.invalidateQueries( + api.lists.get.queryFilter({ listId: req.listId }), + ); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useBookmarkLists( - ...opts: Parameters + input?: Parameters[0], + opts?: Parameters[1], ) { - return api.lists.list.useQuery(opts[0], { - ...opts[1], - select: (data) => { - return { data: data.lists, ...listsToTree(data.lists) }; - }, - }); + const api = useTRPC(); + return useQuery( + api.lists.list.queryOptions(input, { + ...opts, + select: (data) => { + return { data: data.lists, ...listsToTree(data.lists) }; + }, + }), + ); } export function augmentBookmarkListsWithInitialData( diff --git a/packages/shared-react/hooks/reader-settings.tsx b/packages/shared-react/hooks/reader-settings.tsx index 2705a050..7258ebe9 100644 --- a/packages/shared-react/hooks/reader-settings.tsx +++ b/packages/shared-react/hooks/reader-settings.tsx @@ -9,6 +9,7 @@ import { useMemo, useState, } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { READER_DEFAULTS, @@ -16,7 +17,7 @@ import { ReaderSettingsPartial, } from "@karakeep/shared/types/readers"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; export interface UseReaderSettingsOptions { /** @@ -39,6 +40,7 @@ export interface UseReaderSettingsOptions { } export function useReaderSettings(options: UseReaderSettingsOptions) { + const api = useTRPC(); const { getLocalOverrides, saveLocalOverrides, @@ -52,8 +54,8 @@ export function useReaderSettings(options: UseReaderSettingsOptions) { const [pendingServerSave, setPendingServerSave] = useState(null); - const { data: serverSettings } = api.users.settings.useQuery(); - const apiUtils = api.useUtils(); + const { data: serverSettings } = useQuery(api.users.settings.queryOptions()); + const queryClient = useQueryClient(); // Load local overrides on mount useEffect(() => { @@ -76,30 +78,33 @@ export function useReaderSettings(options: UseReaderSettingsOptions) { } }, [serverSettings, pendingServerSave]); - const { mutate: updateServerSettings, isPending: isSaving } = - api.users.updateSettings.useMutation({ + const { mutate: updateServerSettings, isPending: isSaving } = useMutation( + api.users.updateSettings.mutationOptions({ onSettled: async () => { - await apiUtils.users.settings.refetch(); + await queryClient.refetchQueries(api.users.settings.pathFilter()); }, - }); + }), + ); // Separate mutation for saving defaults (clears local overrides on success) const { mutate: saveServerSettings, isPending: isSavingDefaults } = - api.users.updateSettings.useMutation({ - onSuccess: () => { - // Clear local and session overrides after successful server save - setLocalOverrides({}); - saveLocalOverrides({}); - onClearSessionOverrides?.(); - }, - onError: () => { - // Clear pending state so we don't show values that failed to persist - setPendingServerSave(null); - }, - onSettled: async () => { - await apiUtils.users.settings.refetch(); - }, - }); + useMutation( + api.users.updateSettings.mutationOptions({ + onSuccess: () => { + // Clear local and session overrides after successful server save + setLocalOverrides({}); + saveLocalOverrides({}); + onClearSessionOverrides?.(); + }, + onError: () => { + // Clear pending state so we don't show values that failed to persist + setPendingServerSave(null); + }, + onSettled: async () => { + await queryClient.refetchQueries(api.users.settings.pathFilter()); + }, + }), + ); // Compute effective settings with precedence: session → local → pendingSave → server → default const settings: ReaderSettings = useMemo( diff --git a/packages/shared-react/hooks/rules.ts b/packages/shared-react/hooks/rules.ts index 8428f883..8bca9d69 100644 --- a/packages/shared-react/hooks/rules.ts +++ b/packages/shared-react/hooks/rules.ts @@ -1,40 +1,53 @@ -import { api } from "../trpc"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType; export function useCreateRule( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.rules.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.rules.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.rules.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.rules.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateRule( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.rules.update.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.rules.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.rules.update.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.rules.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteRule( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.rules.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.rules.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.rules.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.rules.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/tags.ts b/packages/shared-react/hooks/tags.ts index e1e7416f..a616b88a 100644 --- a/packages/shared-react/hooks/tags.ts +++ b/packages/shared-react/hooks/tags.ts @@ -1,120 +1,155 @@ -import { keepPreviousData } from "@tanstack/react-query"; +import { + keepPreviousData, + useInfiniteQuery, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import { ZTagListResponse } from "@karakeep/shared/types/tags"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType; export function usePaginatedSearchTags( - input: Parameters[0], + input: Parameters[0], ) { - return api.tags.list.useInfiniteQuery(input, { - placeholderData: keepPreviousData, - getNextPageParam: (lastPage) => lastPage.nextCursor, + const api = useTRPC(); + return useInfiniteQuery({ + ...api.tags.list.infiniteQueryOptions(input, { + placeholderData: keepPreviousData, + getNextPageParam: (lastPage) => lastPage.nextCursor, + gcTime: 60_000, + }), select: (data) => ({ tags: data.pages.flatMap((page) => page.tags), }), - gcTime: 60_000, }); } -export function useTagAutocomplete(opts: { +export function useTagAutocomplete(opts: { nameContains: string; - select?: (tags: ZTagListResponse) => T; + select?: (data: ZTagListResponse) => T; enabled?: boolean; }) { - return api.tags.list.useQuery( - { - nameContains: opts.nameContains, - limit: 50, - sortBy: opts.nameContains ? "relevance" : "usage", - }, - { - select: opts.select, - placeholderData: keepPreviousData, - gcTime: opts.nameContains?.length > 0 ? 60_000 : 3_600_000, - enabled: opts.enabled, - }, - ); + const api = useTRPC(); + return useQuery({ + ...api.tags.list.queryOptions( + { + nameContains: opts.nameContains, + limit: 50, + sortBy: opts.nameContains ? "relevance" : "usage", + }, + { + placeholderData: keepPreviousData, + gcTime: opts.nameContains?.length > 0 ? 60_000 : 3_600_000, + enabled: opts.enabled, + }, + ), + select: opts.select, + }); } export function useCreateTag( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateTag( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.update.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - apiUtils.tags.get.invalidate({ tagId: res.id }); - apiUtils.bookmarks.getBookmarks.invalidate({ tagId: res.id }); - - // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks - apiUtils.bookmarks.getBookmark.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.update.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + queryClient.invalidateQueries( + api.tags.get.queryFilter({ tagId: res.id }), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ tagId: res.id }), + ); + + // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks + queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useMergeTag( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.merge.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - [res.mergedIntoTagId, ...res.deletedTags].forEach((tagId) => { - apiUtils.tags.get.invalidate({ tagId }); - apiUtils.bookmarks.getBookmarks.invalidate({ tagId }); - }); - // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks - apiUtils.bookmarks.getBookmark.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.merge.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + [res.mergedIntoTagId, ...res.deletedTags].forEach((tagId) => { + queryClient.invalidateQueries(api.tags.get.queryFilter({ tagId })); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ tagId }), + ); + }); + // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks + queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteTag( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteUnusedTags( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.deleteUnused.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.deleteUnused.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/users.ts b/packages/shared-react/hooks/users.ts index b1909761..221681a4 100644 --- a/packages/shared-react/hooks/users.ts +++ b/packages/shared-react/hooks/users.ts @@ -1,37 +1,49 @@ -import { api } from "../trpc"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType; export function useUpdateUserSettings( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.users.updateSettings.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.users.settings.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.users.updateSettings.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.users.settings.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateUserAvatar( - ...opts: Parameters + opts?: Parameters[0], ) { - const apiUtils = api.useUtils(); - return api.users.updateAvatar.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.users.whoami.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.users.updateAvatar.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.users.whoami.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteAccount( - ...opts: Parameters + opts?: Parameters[0], ) { - return api.users.deleteAccount.useMutation(opts[0]); + const api = useTRPC(); + return useMutation(api.users.deleteAccount.mutationOptions(opts)); } export function useWhoAmI() { - return api.users.whoami.useQuery(); + const api = useTRPC(); + return useQuery(api.users.whoami.queryOptions()); } -- cgit v1.2.3-70-g09d2