Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/solid-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
},
"devDependencies": {
"@solidjs/testing-library": "^0.8.10",
"@solidjs/router": "^0.15.3",
"npm-run-all2": "^5.0.0",
"solid-js": "^1.9.5",
"tsup-preset-solid": "^2.2.0",
Expand Down
77 changes: 77 additions & 0 deletions packages/solid-query/src/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, expectTypeOf, it, vi } from 'vitest'
import {
ErrorBoundary,
Match,
Suspense,
Switch,
createEffect,
createMemo,
Expand All @@ -11,6 +12,7 @@ import {
} from 'solid-js'
import { fireEvent, render, waitFor } from '@solidjs/testing-library'
import { reconcile } from 'solid-js/store'
import { MemoryRouter, Route, createMemoryHistory } from '@solidjs/router'
import { QueryCache, QueryClientProvider, keepPreviousData, useQuery } from '..'
import {
Blink,
Expand Down Expand Up @@ -6045,4 +6047,79 @@ describe('useQuery', () => {

await waitFor(() => rendered.getByText('Status: custom client'))
})

// See https://github.com/tannerlinsley/react-query/issues/8469
it('should correctly manage dependent queries', async () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })

const history = createMemoryHistory()

const errorHandler = vi.fn<(err: unknown) => void>()

function App() {
return (
<ErrorBoundary
fallback={(err) => {
errorHandler(err)
return err.message
}}
>
<Suspense>
<MemoryRouter history={history}>
<Route path="/" component={Index} />
<Route path="/sub" component={Sub} />
</MemoryRouter>
</Suspense>
</ErrorBoundary>
)
}

queryClient.setQueryData(['parent'], { id: 123 })

function Index() {
return 'Index'
}

function Sub() {
const parent = useQuery(() => ({
queryKey: ['parent'],
async queryFn() {
await new Promise((r) => setTimeout(r, 100))
return {
id: 123,
}
},
}))

const childQuery = useQuery(() => ({
queryKey: ['sub', parent.data?.id],
async queryFn() {
await new Promise((r) => setTimeout(r, 200))
return Promise.resolve('child' + parent.data?.id)
},
}))
return <pre>{childQuery.data}</pre>
}

const rendered = render(() => (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
))

await waitFor(() => rendered.getByText('Index'))

history.set({
value: '/sub',
})

await sleep(200)

expect(errorHandler).not.toHaveBeenCalled()

await waitFor(() => {
expect(rendered.getByText('child123')).toBeInTheDocument()
})
Comment on lines +6117 to +6123

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using sleep in tests can introduce flakiness. It's generally better to rely on waitFor to check for the expected outcome.

In this case, if the waitFor assertion for the success state (child123) passes, it implies that no error was thrown and the errorHandler was not called. You can safely move the expect(errorHandler).not.toHaveBeenCalled() assertion after the waitFor block and remove the sleep, making the test more robust.

    await waitFor(() => {
      expect(rendered.getByText('child123')).toBeInTheDocument()
    })

    expect(errorHandler).not.toHaveBeenCalled()

})
})
6 changes: 5 additions & 1 deletion packages/solid-query/src/useBaseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ export function useBaseQuery<
) {
type ResourceData = QueryObserverResult<TData, TError>

const client = createMemo(() => useQueryClient(queryClient?.()))
const client = createMemo(
() => useQueryClient(queryClient?.()),
useQueryClient(queryClient?.()),
)
Comment on lines +119 to +122
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query Client Equality

Initial value parameter in createMemo causes equality comparison between function result and initial value on every evaluation. This breaks memoization when query client instances differ, leading to unnecessary re-computations and potential query observer recreation.

  const client = createMemo(
    () => useQueryClient(queryClient?.())
  )
Commitable Suggestion
Suggested change
const client = createMemo(
() => useQueryClient(queryClient?.()),
useQueryClient(queryClient?.()),
)
const client = createMemo(
() => useQueryClient(queryClient?.())
)
Standards
  • ISO-IEC-25010-Reliability-Maturity
  • SRE-Performance-Reliability

Comment on lines +119 to +122
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memo Equality Comparison

Memo equality comparison uses function call result as equality check causing unnecessary re-computations. The useQueryClient call executes on every memo evaluation instead of providing stable equality reference.

Standards
  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Optimization-Pattern-Memoization-Efficiency


const isRestoring = useIsRestoring()
// There are times when we run a query on the server but the resource is never read
// This could lead to times when the queryObserver is unsubscribed before the resource has loaded
Expand Down
71 changes: 37 additions & 34 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading