Skip to content

Commit

Permalink
fix(useQuery): fix onError params
Browse files Browse the repository at this point in the history
  • Loading branch information
vikiboss committed Oct 11, 2024
1 parent ccd107e commit 921f947
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 51 deletions.
4 changes: 3 additions & 1 deletion packages/react-use/src/use-query/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ function Demo5() {
immediate: false,
cacheKey: 'cacheKeyForDemo5',
// provider: localStorageProvider,
onErrorRetry: (error, { currentCount }) => toast.error(`Retry ${currentCount} failed.`),
errorRetryCount: 3,
onErrorRetry: (error, { currentCount }) => toast.loading(`Retry ${currentCount} times ...`, { id: 'retry' }),
onSuccess: (data, params) => toast.success(`Success! ${data}`, { id: 'retry' }),
},
)

Expand Down
128 changes: 89 additions & 39 deletions packages/react-use/src/use-query/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,111 @@
import { act, renderHook } from '@/test'
// packages/react-use/src/use-query/index.test.ts
import { type Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useQuery } from './index'

import type { Mock } from 'vitest'

describe('useQuery', () => {
let fetcher: Mock
let options: any
let mockFetcher: Mock

beforeEach(() => {
mockFetcher = vi.fn().mockResolvedValue('data')
vi.useFakeTimers()
fetcher = vi.fn()
options = {}
})

afterEach(() => {
vi.clearAllMocks()
vi.useRealTimers()
})

it('should initialize with default values', () => {
const { result } = renderHook(() => useQuery(fetcher, options))
it('should initialize with default values when ser manual', async () => {
const { result } = renderHook(() => useQuery(mockFetcher, { manual: true }))

expect(mockFetcher).not.toHaveBeenCalled()

expect(result.current.data).toBeUndefined()
expect(result.current.loading).toBe(false)
expect(result.current.error).toBeUndefined()
expect(result.current.initializing).toBe(false)
expect(result.current.refreshing).toBe(false)
expect(result.current.loadingSlow).toBe(false)
expect(result.current.params).toEqual([])

expect(result.current.run).toBeDefined()
expect(result.current.refresh).toBeInstanceOf(Function)
expect(result.current.cancel).toBeInstanceOf(Function)
expect(result.current.mutate).toBeInstanceOf(Function)
expect(result.current.pause).toBeInstanceOf(Function)
expect(result.current.resume).toBeInstanceOf(Function)
expect(result.current.isActive).toBeInstanceOf(Function)
expect(result.current.isActive()).toBe(true)
})

it('should handle fetcher when running', async () => {
const { result } = renderHook(() => useQuery(mockFetcher))

expect(mockFetcher).not.toHaveBeenCalled() // promise not resolved yet

expect(result.current.data).toBeUndefined()
expect(result.current.loading).toBe(true)
expect(result.current.error).toBeUndefined()
expect(result.current.initializing).toBe(true)
expect(result.current.refreshing).toBe(false)
expect(result.current.loadingSlow).toBe(false)
expect(result.current.params).toEqual([])
})

it('should fetch data successfully', async () => {
fetcher.mockResolvedValueOnce('data')
const { result } = renderHook(() => useQuery(fetcher, options))
it('should handle fetcher when operation done', async () => {
const { result } = renderHook(() => useQuery(mockFetcher))

await act(async () => {}) // wait for fetcher to resolve

await act(async () => {})
expect(mockFetcher).toHaveBeenCalled()

expect(fetcher).toHaveBeenCalled()
expect(result.current.data).toBe('data')
expect(result.current.loading).toBe(false)
expect(result.current.error).toBeUndefined()
expect(result.current.initializing).toBe(false)
expect(result.current.refreshing).toBe(false)
expect(result.current.loadingSlow).toBe(false)
expect(result.current.params).toEqual([])
})

it.skip('should handle fetch error', async () => {
fetcher.mockRejectedValue(new Error('fetch error'))
const { result } = renderHook(() => useQuery(fetcher, options))
it('should handle fetch error', async () => {
mockFetcher.mockRejectedValue(new Error('fetch error'))

const { result } = renderHook(() => useQuery(mockFetcher))

await act(async () => {})
await act(async () => {}) // wait for fetcher to reject

expect(fetcher).toHaveBeenCalled()
expect(result.current.error).toBeInstanceOf(Error) // FIXME: fix it
expect(mockFetcher).toHaveBeenCalled()
expect(result.current.error).toBeInstanceOf(Error)
expect(result.current.loading).toBe(false)
})

it('should handle dependencies refresh', async () => {
const { result, rerender } = renderHook(({ id }) => useQuery(mockFetcher, { refreshDependencies: [id] }), {
initialProps: { id: 1 },
})

await act(async () => {}) // wait for fetcher to resolve

expect(result.current.data).toBe('data')

mockFetcher.mockResolvedValueOnce('newData')

rerender({ id: 2 })

expect(result.current.data).toBe('data') // not updated yet && not clear previous data

await act(async () => {}) // wait for fetcher to resolve

expect(result.current.data).toBe('newData')
})

it('should refresh data on focus', async () => {
fetcher.mockResolvedValueOnce('data')
options.refreshOnFocus = true
const { result } = renderHook(() => useQuery(fetcher, options))
const { result } = renderHook(() => useQuery(mockFetcher, { refreshOnFocus: true }))

await act(async () => {})
await act(async () => {}) // wait for fetcher to reject

expect(result.current.data).toBe('data')

Expand All @@ -62,15 +114,13 @@ describe('useQuery', () => {
window.dispatchEvent(new FocusEvent('focus'))
})

expect(fetcher).toHaveBeenCalledTimes(2)
expect(mockFetcher).toHaveBeenCalledTimes(2)
})

it('should not refresh data on focus if disabled', async () => {
fetcher.mockResolvedValueOnce('data')
options.refreshOnFocus = false
const { result } = renderHook(() => useQuery(fetcher, options))
const { result } = renderHook(() => useQuery(mockFetcher, { refreshOnFocus: false }))

await act(async () => {})
await act(async () => {}) // wait for fetcher to reject

expect(result.current.data).toBe('data')

Expand All @@ -79,15 +129,13 @@ describe('useQuery', () => {
window.dispatchEvent(new FocusEvent('focus'))
})

expect(fetcher).toHaveBeenCalledTimes(1)
expect(mockFetcher).toHaveBeenCalledTimes(1)
})

it('should handle manual refresh', async () => {
fetcher.mockResolvedValueOnce('data')
options.manual = true
const { result } = renderHook(() => useQuery(fetcher, options))
const { result } = renderHook(() => useQuery(mockFetcher, { manual: true }))

await act(async () => {})
await act(async () => {}) // wait for fetcher to reject

expect(result.current.data).toBeUndefined()

Expand All @@ -99,12 +147,14 @@ describe('useQuery', () => {
})

it('should respect cache expiration', async () => {
fetcher.mockResolvedValue('data')
options.cacheKey = 'test'
options.cacheExpirationTime = 100 // 100 ms
const { result } = renderHook(() => useQuery(fetcher, options))

await act(async () => {})
const { result } = renderHook(() =>
useQuery(mockFetcher, {
cacheKey: 'test',
cacheExpirationTime: 100,
}),
)

await act(async () => {}) // wait for fetcher to reject

expect(result.current.data).toBe('data')

Expand Down
25 changes: 14 additions & 11 deletions packages/react-use/src/use-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,12 @@ export function useQuery<T extends AnyFunc, D = Awaited<ReturnType<T>>, E = any>
count: options.errorRetryCount ?? 0,
interval: options.errorRetryInterval,
onErrorRetry: options.onErrorRetry,
onRetryFailed: options.onErrorRetryFailed,
onRetryFailed(...args) {
latest.current.onErrorRetryFailed?.(...args)
throw args[0]
},
onError(...args) {
cacheActions.clearPromiseCache()
return latest.current.onError?.(...args)
},
},
),
Expand All @@ -222,29 +224,30 @@ export function useQuery<T extends AnyFunc, D = Awaited<ReturnType<T>>, E = any>
initialValue: options.initialData ?? cache.data,
clearBeforeRun: options.clearBeforeRun,
loadingTimeout: options.loadingTimeout,
onBefore(...args) {
if (latest.current.clearBeforeRun) cacheActions.setCache(undefined, [])
intervalPausable.resume()
return latest.current.onBefore?.(...args)
},
onLoadingSlow: options.onLoadingSlow,
onRefresh: options.onRefresh,
onCancel(...args) {
cacheActions.clearPromiseCache()
return latest.current.onCancel?.(...args)
},
onFinally(...args) {
cacheActions.clearPromiseCache()
return latest.current.onFinally?.(...args)
},
onSuccess(nextData, params, ...rest) {
cacheActions.setCache(nextData, params)
return latest.current.onSuccess?.(nextData, params, ...rest)
},
onBefore(...args) {
if (latest.current.clearBeforeRun) cacheActions.setCache(undefined, [])
intervalPausable.resume()
return latest.current.onBefore?.(...args)
onError: options.onError,
onFinally(...args) {
cacheActions.clearPromiseCache()
return latest.current.onFinally?.(...args)
},
onMutate(nextData, params, ...rest) {
cacheActions.setCache(nextData, params)
return latest.current.onMutate?.(nextData, params, ...rest)
},
onRefresh: options.onRefresh,
},
)

Expand Down

0 comments on commit 921f947

Please sign in to comment.