Skip to content

fix(react-query): move away of uSES #8434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 67 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
7d3fccc
wip
KATT Dec 13, 2024
bc36411
wip
KATT Dec 13, 2024
63c548c
wip
KATT Dec 13, 2024
3ed5349
ci: apply automated fixes
autofix-ci[bot] Dec 13, 2024
784cb83
wip
KATT Dec 13, 2024
fb21469
wip
KATT Dec 13, 2024
a7ba6bc
kinda works
KATT Dec 13, 2024
b6917d2
apparently this fixes stuff
KATT Dec 13, 2024
71ccdcf
ok works
KATT Dec 13, 2024
0faec03
ci: apply automated fixes
autofix-ci[bot] Dec 13, 2024
16e9c59
bump
KATT Dec 13, 2024
510bcbe
wip
KATT Dec 13, 2024
76fd305
Merge branch 'issues/8384' of github.com:KATT/react-query into issues…
KATT Dec 13, 2024
f035539
ci: apply automated fixes
autofix-ci[bot] Dec 13, 2024
ec4d91e
uhm
KATT Dec 13, 2024
bd25f47
Merge branch 'issues/8384' of github.com:KATT/react-query into issues…
KATT Dec 13, 2024
3002562
ci: apply automated fixes
autofix-ci[bot] Dec 13, 2024
61d9be3
wip
KATT Dec 13, 2024
46d145c
wip
KATT Dec 13, 2024
201169a
reset
KATT Dec 13, 2024
3685c95
reset
KATT Dec 13, 2024
a1890ed
hi
KATT Dec 13, 2024
123d6e4
ci: apply automated fixes
autofix-ci[bot] Dec 13, 2024
cc5d27f
wip
KATT Dec 13, 2024
f5c1e5d
Merge branch 'issues/8384' of github.com:KATT/react-query into issues…
KATT Dec 13, 2024
ab24be3
act fix
KATT Dec 13, 2024
27d348c
delete lib
KATT Dec 13, 2024
e2dca98
cleanup
KATT Dec 13, 2024
c0d3680
meep
KATT Dec 13, 2024
77f4616
less diff
KATT Dec 13, 2024
a1e2dd8
less diff
KATT Dec 13, 2024
50dfe93
cool
KATT Dec 13, 2024
3f6748a
mkay
KATT Dec 13, 2024
6e4a559
ci: apply automated fixes
autofix-ci[bot] Dec 13, 2024
2649b87
fucking lint
KATT Dec 13, 2024
d878172
revert
KATT Dec 13, 2024
4b9c5d8
test: startTransition
TkDodo Dec 16, 2024
19f7230
Merge branch 'feature/transition-tests' of github.com:TanStack/query …
KATT Dec 19, 2024
38b8864
fix test
KATT Dec 19, 2024
84dffdc
rm dep array
KATT Dec 19, 2024
a728d6b
mkay
KATT Dec 19, 2024
58f0f77
Merge branch 'main' into issues/8384
KATT Dec 19, 2024
984fa4b
failing test
KATT Dec 19, 2024
cfdf512
Merge branch 'issues/8384' of github.com:KATT/react-query into issues…
KATT Dec 19, 2024
ed313f7
mkay better test
KATT Dec 19, 2024
dcf16f2
might revert this later
KATT Dec 19, 2024
60f6736
prettier
KATT Dec 19, 2024
5a97880
Revert "might revert this later"
KATT Dec 19, 2024
aba6e7f
wait
KATT Dec 19, 2024
b5d30af
mkay
KATT Dec 19, 2024
e6a7d00
vscode helps
KATT Dec 20, 2024
847e7da
why doesn’t this failllll
KATT Dec 20, 2024
cb449e0
org
KATT Dec 20, 2024
4a5296c
add transition
KATT Dec 20, 2024
c0d341e
tweak
KATT Dec 20, 2024
02d9727
fix
KATT Dec 20, 2024
442c4d5
mkay
KATT Dec 20, 2024
f93377b
pendy
KATT Dec 20, 2024
76bfacf
revert useIsFetching etc etc
KATT Dec 20, 2024
efed3ff
does this fail?
KATT Dec 20, 2024
5a1358e
idk
KATT Dec 20, 2024
30e819b
wip
KATT Dec 20, 2024
83dddf0
fix test
KATT Dec 20, 2024
7e7d55c
ok
KATT Dec 20, 2024
7df5d81
test: add test case for 8249
TkDodo Jan 3, 2025
9096707
rest: re-order describe blocks to avoid "act not available in environ…
TkDodo Jan 3, 2025
9797e93
Merge branch 'main' into issues/8384
TkDodo Jan 3, 2025
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: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@ size-plugin.json
stats-hydration.json
stats.json
stats.html
.vscode/settings.json

*.log
*.tsbuildinfo
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
Comment on lines +1 to +4
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Feel free to remove this, but it helps me as a contributor - I have a bunch of settings in trpc https://github.com/trpc/trpc/tree/next/.vscode

3 changes: 3 additions & 0 deletions examples/react/transition/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["plugin:react/jsx-runtime", "plugin:react-hooks/recommended"]
}
27 changes: 27 additions & 0 deletions examples/react/transition/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

pnpm-lock.yaml
yarn.lock
package-lock.json

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
6 changes: 6 additions & 0 deletions examples/react/transition/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Example

To run this example:

- `pnpm install`
- `pnpm dev`
16 changes: 16 additions & 0 deletions examples/react/transition/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" type="image/svg+xml" href="/emblem-light.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />

<title>TanStack Query React Suspense Example App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
21 changes: 21 additions & 0 deletions examples/react/transition/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@tanstack/query-example-react-transition",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.62.8",
"@tanstack/react-query-devtools": "^5.62.8",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.3",
"typescript": "5.7.2",
"vite": "^5.3.5"
}
}
13 changes: 13 additions & 0 deletions examples/react/transition/public/emblem-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions examples/react/transition/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
import { Suspense, use, useState, useTransition } from 'react'
import ReactDOM from 'react-dom/client'

const Example1 = ({ value }: { value: number }) => {
const { isFetching, promise } = useQuery({
queryKey: ['1' + value],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return '1' + value
},
})
const data = use(promise)

return (
<div>
{data} {isFetching ? 'fetching' : null}
</div>
)
}

const Example2 = ({ value }: { value: number }) => {
const { promise, isFetching } = useQuery({
queryKey: ['2' + value],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return '2' + value
},
// placeholderData: keepPreviousData,
})

const data = use(promise)

return (
<div>
{data} {isFetching ? 'fetching' : null}
</div>
)
}

const SuspenseBoundary = () => {
const [state, setState] = useState(-1)
const [isPending, startTransition] = useTransition()

return (
<div>
<h1>Change state with transition</h1>
<div>
<button
onClick={() =>
startTransition(() => {
setState((s) => s - 1)
})
}
>
Decrease
</button>
</div>
<h2>State:</h2>
<ul>
<li>last state value: {state}</li>
<li>
transition state: {isPending ? <strong>pending</strong> : 'idle'}
</li>
</ul>
<h2>2. 1 Suspense + startTransition</h2>
<Suspense fallback="fallback 1">
<Example1 value={state}></Example1>
</Suspense>
<h2>2.2 Suspense + startTransition</h2>
<Suspense fallback="fallback 2">
<Example2 value={state}></Example2>
</Suspense>
</div>
)
}

const queryClient = new QueryClient({
defaultOptions: {
queries: {
experimental_prefetchInRender: true,
staleTime: 10 * 1000,
},
},
})

const App = () => {
return (
<div>
<QueryClientProvider client={queryClient}>
<SuspenseBoundary />
</QueryClientProvider>
</div>
)
}

const rootElement = document.getElementById('root') as HTMLElement
ReactDOM.createRoot(rootElement).render(<App />)
24 changes: 24 additions & 0 deletions examples/react/transition/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "eslint.config.js"]
}
6 changes: 6 additions & 0 deletions examples/react/transition/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()],
})
4 changes: 2 additions & 2 deletions packages/query-core/src/queryObserver.ts
Original file line number Diff line number Diff line change
@@ -698,11 +698,11 @@
includedProps.add('error')
}

return Object.keys(this.#currentResult).some((key) => {
return [...includedProps].some((key) => {

Check warning on line 701 in packages/query-core/src/queryObserver.ts

Codecov / codecov/patch

packages/query-core/src/queryObserver.ts#L701

Added line #L701 was not covered by tests
const typedKey = key as keyof QueryObserverResult
const changed = this.#currentResult[typedKey] !== prevResult[typedKey]

return changed && includedProps.has(typedKey)
return changed

Check warning on line 705 in packages/query-core/src/queryObserver.ts

Codecov / codecov/patch

packages/query-core/src/queryObserver.ts#L705

Added line #L705 was not covered by tests
})
}

288 changes: 288 additions & 0 deletions packages/react-query/src/__tests__/transition.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
/* eslint-disable @typescript-eslint/require-await */
import { act, render, screen } from '@testing-library/react'
import * as React from 'react'
import { afterAll, beforeAll, expect, it, vi } from 'vitest'
import { QueryClientProvider, useQuery } from '..'
import { QueryCache } from '../index'
import { createQueryClient, queryKey, sleep } from './utils'

const queryCache = new QueryCache()
const queryClient = createQueryClient({
queryCache,
})

beforeAll(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
global.IS_REACT_ACT_ENVIRONMENT = true
Comment on lines +15 to +17
Copy link
Contributor Author

@KATT KATT Dec 20, 2024

Choose a reason for hiding this comment

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

queryClient.setDefaultOptions({
queries: { experimental_prefetchInRender: true },
})
vi.useFakeTimers()
})
afterAll(() => {
queryClient.setDefaultOptions({
queries: { experimental_prefetchInRender: false },
})
vi.useRealTimers()
})

it('should keep values of old key around with startTransition', async () => {
const key = queryKey()

function Loading() {
return <>loading...</>
}

function Page() {
const [isPending, startTransition] = React.useTransition()
const [count, setCount] = React.useState(0)
const query = useQuery({
queryKey: [key, count],
queryFn: async () => {
await sleep(10)
return 'test' + count
},
staleTime: 1000,
})

const data = React.use(query.promise)

return (
<div>
<button onClick={() => startTransition(() => setCount((c) => c + 1))}>
increment
</button>
<div>data: {data}</div>
{isPending && <span>pending...</span>}
</div>
)
}
// Initial render should show fallback
await act(async () => {
render(
<QueryClientProvider client={queryClient}>
<React.Suspense fallback={<Loading />}>
<Page />
</React.Suspense>
</QueryClientProvider>,
)
})

screen.getByText('loading...')
expect(screen.queryByText('button')).toBeNull()
expect(screen.queryByText('pending...')).toBeNull()
expect(screen.queryByText('data: test0')).toBeNull()

// Resolve the query, should show the data
await act(async () => {
vi.runAllTimers()
})

expect(screen.queryByText('loading...')).toBeNull()
screen.getByRole('button')
expect(screen.queryByText('pending...')).toBeNull()
screen.getByText('data: test0')

// Update in a transition, should show pending state, and existing content
await act(async () => {
screen.getByRole('button', { name: 'increment' }).click()
})
expect(screen.queryByText('loading...')).toBeNull()
screen.getByRole('button')
screen.getByText('pending...')
screen.getByText('data: test0')

// Resolve the query, should show the new data and no pending state
await act(async () => {
vi.runAllTimers()
})
expect(screen.queryByText('loading...')).toBeNull()
screen.getByRole('button')
expect(screen.queryByText('pending...')).toBeNull()
screen.getByText('data: test1')
})

it('should handle parallel queries with shared parent key in transition', async () => {
function ComponentA(props: { parentId: number }) {
const query = useQuery({
queryKey: ['A', props.parentId],
queryFn: async () => {
await sleep(10)
return `A-${props.parentId}`
},
staleTime: 1000,
})

const data = React.use(query.promise)
return <div>A data: {data}</div>
}

function ComponentALoading() {
return <div>A loading...</div>
}

function ComponentB(props: { parentId: number }) {
const query = useQuery({
queryKey: ['B', props.parentId],
queryFn: async () => {
await sleep(10)
return `B-${props.parentId}`
},
staleTime: 1000,
})

const data = React.use(query.promise)
return <div>B data: {data}</div>
}

function ComponentBLoading() {
return <div>B loading...</div>
}

function Parent() {
const [count, setCount] = React.useState(0)
const [isPending, startTransition] = React.useTransition()
return (
<div>
<button onClick={() => startTransition(() => setCount((c) => c + 1))}>
increment
</button>
<React.Suspense fallback={<ComponentALoading />}>
<ComponentA parentId={count} />
</React.Suspense>
<React.Suspense fallback={<ComponentBLoading />}>
<ComponentB parentId={count} />
</React.Suspense>
{isPending && <span>pending...</span>}
</div>
)
}

// Initial render should show fallback
await act(async () => {
render(
<QueryClientProvider client={queryClient}>
<Parent />
</QueryClientProvider>,
)
})

screen.getByText('A loading...')
screen.getByText('B loading...')

// Resolve the query, should show the data
await act(async () => {
vi.runAllTimers()
})

screen.getByText('A data: A-0')
screen.getByText('B data: B-0')

// Update in a transition, should show pending state, and existing content
await act(async () => {
screen.getByRole('button', { name: 'increment' }).click()
})

screen.getByText('pending...')
screen.getByText('A data: A-0')
screen.getByText('B data: B-0')

// Resolve the query, should show the new data and no pending state
await act(async () => {
vi.runAllTimers()
})
screen.getByText('A data: A-1')
screen.getByText('B data: B-1')
expect(screen.queryByText('pending...')).toBeNull()
})

it('should work to interrupt a transition', async () => {
const resolversByCount: Record<number, () => void> = {}

const key = queryKey()

function Component(props: { count: number }) {
const { count } = props

const query = useQuery({
queryKey: [key, count],
queryFn: async () => {
await new Promise<void>((resolve) => {
resolversByCount[count] = resolve
})

return 'test' + count
},
staleTime: 1000,
})
const data = React.use(query.promise)
return <div>data: {data}</div>
}

function Page() {
const [isPending, startTransition] = React.useTransition()
const [count, setCount] = React.useState(0)

return (
<div>
<button onClick={() => startTransition(() => setCount((c) => c + 1))}>
increment
</button>
<React.Suspense fallback="loading...">
<Component count={count} />
</React.Suspense>
{isPending && 'pending...'}
</div>
)
}
// Initial render should show fallback
await act(async () => {
render(
<QueryClientProvider client={queryClient}>
<Page />
</QueryClientProvider>,
)
})

screen.getByText('loading...')
expect(screen.queryByText('button')).toBeNull()
expect(screen.queryByText('pending...')).toBeNull()
expect(screen.queryByText('data: test0')).toBeNull()

// Resolve the query, should show the data
await act(async () => {
resolversByCount[0]!()
})

screen.getByText('data: test0')

// increment
await act(async () => {
screen.getByRole('button', { name: 'increment' }).click()
})

// should show pending state, and existing content
screen.getByText('pending...')
screen.getByText('data: test0')

// Before the query is resolved, increment again
await act(async () => {
screen.getByRole('button', { name: 'increment' }).click()
})

await act(async () => {
// resolve the second query
resolversByCount[1]!()
})

screen.getByText('pending...')
screen.getByText('data: test0')

await act(async () => {
// resolve the third query
resolversByCount[2]!()
})

screen.getByText('data: test2')
})
2,509 changes: 1,312 additions & 1,197 deletions packages/react-query/src/__tests__/useQuery.promise.test.tsx

Large diffs are not rendered by default.

109 changes: 49 additions & 60 deletions packages/react-query/src/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { dehydrate, hydrate, skipToken } from '@tanstack/query-core'
import { QueryCache, keepPreviousData, useQuery } from '..'
import {
Blink,
arrayPick,
createQueryClient,
mockOnlineManagerIsOnline,
mockVisibilityState,
@@ -2485,38 +2486,6 @@ describe('useQuery', () => {
expect(queryCache.find({ queryKey: key })!.options.retryDelay).toBe(20)
})

it('should batch re-renders', async () => {
Copy link
Contributor Author

@KATT KATT Dec 13, 2024

Choose a reason for hiding this comment

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

This test doesn't work anymore when we're force rendering

I'm guessing we just have to trust the concurrency in React to do this in an optimal way?

const key = queryKey()

let renders = 0

const queryFn = async () => {
await sleep(15)
return 'data'
}

function Page() {
const query1 = useQuery({ queryKey: key, queryFn })
const query2 = useQuery({ queryKey: key, queryFn })
renders++

return (
<div>
{query1.data} {query2.data}
</div>
)
}

const rendered = renderWithClient(queryClient, <Page />)

await waitFor(() => {
rendered.getByText('data data')
})

// Should be 2 instead of 3
expect(renders).toBe(2)
})

it('should render latest data even if react has discarded certain renders', async () => {
const key = queryKey()

@@ -4803,6 +4772,7 @@ describe('useQuery', () => {
return count
},
staleTime: Infinity,
notifyOnChangeProps: 'all',
})

states.push(state)
@@ -4829,34 +4799,53 @@ describe('useQuery', () => {

expect(count).toBe(2)

expect(states[0]).toMatchObject({
data: undefined,
isPending: true,
isFetching: true,
isSuccess: false,
isStale: true,
})
expect(states[1]).toMatchObject({
data: 1,
isPending: false,
isFetching: false,
isSuccess: true,
isStale: false,
})
expect(states[2]).toMatchObject({
data: undefined,
isPending: true,
isFetching: true,
isSuccess: false,
isStale: true,
})
expect(states[3]).toMatchObject({
data: 2,
isPending: false,
isFetching: false,
isSuccess: true,
isStale: false,
})
expect(
arrayPick(states, [
'data',
'isStale',
'isFetching',
'isPending',
'isSuccess',
]),
).toMatchInlineSnapshot(`
[
{
"data": undefined,
"isFetching": true,
"isPending": true,
"isStale": true,
"isSuccess": false,
},
{
"data": 1,
"isFetching": false,
"isPending": false,
"isStale": false,
"isSuccess": true,
},
{
"data": undefined,
"isFetching": true,
"isPending": true,
"isStale": true,
"isSuccess": false,
},
{
"data": undefined,
"isFetching": true,
"isPending": true,
"isStale": true,
"isSuccess": false,
},
{
"data": 2,
"isFetching": false,
"isPending": false,
"isStale": false,
"isSuccess": true,
},
]
`)
})

it('should update query state and not refetch when resetting a disabled query with resetQueries', async () => {
20 changes: 20 additions & 0 deletions packages/react-query/src/__tests__/utils.tsx
Original file line number Diff line number Diff line change
@@ -94,3 +94,23 @@ export function setIsServer(isServer: boolean) {
}

export const doNotExecute = (_func: () => void) => true

function pick<T extends object, TKey extends keyof T>(
obj: T,
keys: Array<TKey>,
): Pick<T, TKey> {
return keys.reduce(
(acc, key) => {
acc[key] = obj[key]
return acc
},
{} as Pick<T, TKey>,
)
}

export function arrayPick<T extends object, TKey extends keyof T>(
list: Array<T>,
keys: Array<TKey>,
): Array<Pick<T, TKey>> {
return list.map((item) => pick(item, keys))
}
47 changes: 29 additions & 18 deletions packages/react-query/src/useBaseQuery.ts
Original file line number Diff line number Diff line change
@@ -82,33 +82,44 @@
),
)

const [_, setForceUpdate] = React.useState({})

const result = observer.getOptimisticResult(defaultedOptions)

React.useSyncExternalStore(
React.useCallback(
(onStoreChange) => {
const unsubscribe = isRestoring
? noop
: observer.subscribe(notifyManager.batchCalls(onStoreChange))

// Update result to make sure we did not miss any query updates
// between creating the observer and subscribing to it.
observer.updateResult()

return unsubscribe
},
[observer, isRestoring],
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult(),
)
React.useEffect(() => {
if (isRestoring) {
return

Check warning on line 91 in packages/react-query/src/useBaseQuery.ts

Codecov / codecov/patch

packages/react-query/src/useBaseQuery.ts#L91

Added line #L91 was not covered by tests
}

const unsubscribe = observer.subscribe(
notifyManager.batchCalls(() => {
setForceUpdate({})
}),
)

// Update result to make sure we did not miss any query updates
// between creating the observer and subscribing to it.
observer.updateResult()

return unsubscribe
}, [observer, isRestoring])

React.useEffect(() => {
if (defaultedOptions.experimental_prefetchInRender) {
return
}
// Do not notify on updates because of changes in the options because
// these changes should already be reflected in the optimistic result.
observer.setOptions(defaultedOptions, { listeners: false })
}, [defaultedOptions, observer])

// For prefetchInRender, we need to set the options within the render
if (defaultedOptions.experimental_prefetchInRender) {
// Do not notify on updates because of changes in the options because
// these changes should already be reflected in the optimistic result.
observer.setOptions(defaultedOptions, { listeners: false })
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Setting the options straight away for experimental_prefetchInRender seems to fix it.

I think the issue I had was some race condition...

I still have no reliable reproduction though

}

// Handle suspense
if (shouldSuspend(defaultedOptions, result)) {
throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
1 change: 0 additions & 1 deletion packages/react-query/vite.config.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ export default defineConfig({
test: {
name: packageJson.name,
dir: './src',
watch: false,
environment: 'jsdom',
setupFiles: ['test-setup.ts'],
coverage: { enabled: true, provider: 'istanbul', include: ['src/**/*'] },
25 changes: 25 additions & 0 deletions pnpm-lock.yaml