diff --git a/src/renderer/src/data/hooks/useDataApi.ts b/src/renderer/src/data/hooks/useDataApi.ts index 2ce7e34333..a4a73230e4 100644 --- a/src/renderer/src/data/hooks/useDataApi.ts +++ b/src/renderer/src/data/hooks/useDataApi.ts @@ -5,7 +5,7 @@ import { type OffsetPaginatedResponse, type PaginatedResponse } from '@shared/data/api/apiTypes' -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { KeyedMutator } from 'swr' import useSWR, { useSWRConfig } from 'swr' import useSWRInfinite from 'swr/infinite' @@ -138,6 +138,17 @@ function getFetcher([path, query]: [TPath, Recor return apiFetcher(path, { query }) } +/** + * Default SWR configuration options shared across hooks + */ +const DEFAULT_SWR_OPTIONS = { + revalidateOnFocus: false, + revalidateOnReconnect: true, + dedupingInterval: 5000, + errorRetryCount: 3, + errorRetryInterval: 1000 +} as const + /** * Data fetching hook with SWR caching and revalidation * @@ -159,17 +170,11 @@ export function useQuery( const key = options?.enabled !== false ? buildSWRKey(path, options?.query as Record) : null const { data, error, isLoading, isValidating, mutate } = useSWR(key, getFetcher, { - revalidateOnFocus: false, - revalidateOnReconnect: true, - dedupingInterval: 5000, - errorRetryCount: 3, - errorRetryInterval: 1000, + ...DEFAULT_SWR_OPTIONS, ...options?.swrOptions }) - const refetch = () => { - mutate() - } + const refetch = useCallback(() => mutate(), [mutate]) return { data, @@ -209,6 +214,12 @@ export function useMutation { const { mutate: globalMutate } = useSWRConfig() + // Use ref to avoid stale closure issues with callbacks + const optionsRef = useRef(options) + useEffect(() => { + optionsRef.current = options + }, [options]) + const apiFetcher = createApiFetcher(method) const fetcher = async ( @@ -229,25 +240,24 @@ export function useMutation { - options?.onSuccess?.(data) + optionsRef.current?.onSuccess?.(data) - if (options?.revalidate === true) { + if (optionsRef.current?.revalidate === true) { await globalMutate(() => true) - } else if (Array.isArray(options?.revalidate)) { - for (const path of options.revalidate) { - await globalMutate(path) - } + } else if (Array.isArray(optionsRef.current?.revalidate)) { + await Promise.all(optionsRef.current.revalidate.map((key) => globalMutate(key))) } }, - onError: options?.onError + onError: (error) => optionsRef.current?.onError?.(error) }) const optimisticMutate = async (data?: { body?: BodyForPath query?: QueryParamsForPath }): Promise> => { - if (options?.optimistic && options?.optimisticData) { - await globalMutate(path, options.optimisticData, false) + const opts = optionsRef.current + if (opts?.optimistic && opts?.optimisticData) { + await globalMutate(path, opts.optimisticData, false) } try { @@ -255,13 +265,13 @@ export function useMutation => { + const invalidate = async (keys?: string | string[] | boolean): Promise => { if (keys === true || keys === undefined) { - return mutate(() => true) + await mutate(() => true) } else if (typeof keys === 'string') { - return mutate(keys) + await mutate(keys) } else if (Array.isArray(keys)) { - return Promise.all(keys.map((key) => mutate(key))) + await Promise.all(keys.map((key) => mutate(key))) } - return Promise.resolve() } return invalidate @@ -398,11 +407,7 @@ export function useInfiniteQuery( } const swrResult = useSWRInfinite(getKey, infiniteFetcher, { - revalidateOnFocus: false, - revalidateOnReconnect: true, - dedupingInterval: 5000, - errorRetryCount: 3, - errorRetryInterval: 1000, + ...DEFAULT_SWR_OPTIONS, initialSize: 1, revalidateAll: false, revalidateFirstPage: true, @@ -469,6 +474,8 @@ export function usePaginatedQuery( query?: Omit, 'page' | 'limit'> /** Items per page (default: 10) */ limit?: number + /** Whether to enable the query (default: true) */ + enabled?: boolean /** SWR options */ swrOptions?: Parameters[2] } @@ -476,6 +483,12 @@ export function usePaginatedQuery( const [currentPage, setCurrentPage] = useState(1) const limit = options?.limit || 10 + // Reset page to 1 when query parameters change + const queryKey = JSON.stringify(options?.query) + useEffect(() => { + setCurrentPage(1) + }, [queryKey]) + const queryWithPagination = { ...options?.query, page: currentPage, @@ -484,6 +497,7 @@ export function usePaginatedQuery( const { data, isLoading, isRefreshing, error, refetch } = useQuery(path, { query: queryWithPagination as QueryParamsForPath, + enabled: options?.enabled, swrOptions: options?.swrOptions })