diff --git a/.changeset/salty-hotels-turn.md b/.changeset/salty-hotels-turn.md new file mode 100644 index 0000000000..8cb3b5d247 --- /dev/null +++ b/.changeset/salty-hotels-turn.md @@ -0,0 +1,6 @@ +--- +'@tanstack/preact-query': patch +'@tanstack/react-query': patch +--- + +ref(HydrationBoundary): remove checks that are guarded by types diff --git a/packages/preact-query/src/HydrationBoundary.tsx b/packages/preact-query/src/HydrationBoundary.tsx index f9cf967b06..101ebb9860 100644 --- a/packages/preact-query/src/HydrationBoundary.tsx +++ b/packages/preact-query/src/HydrationBoundary.tsx @@ -18,7 +18,7 @@ export interface HydrationBoundaryProps { /** * The state to hydrate. */ - state: DehydratedState | null | undefined + state: DehydratedState /** * Optional. Note: unlike `hydrate`, `mutations` cannot be set here. */ @@ -113,49 +113,36 @@ export const HydrationBoundary = ({ // we throw away the fresh data for any existing ones to avoid unexpectedly // updating the UI. const hydrationQueue: DehydratedState['queries'] | undefined = useMemo(() => { - if (state) { - if (typeof state !== 'object') { - return - } - - const queryCache = client.getQueryCache() - // State is supplied from the outside and we might as well fail - // gracefully if it has the wrong shape, so while we type `queries` - // as required, we still provide a fallback. - const queries = state.queries || [] + const queryCache = client.getQueryCache() - const newQueries: DehydratedState['queries'] = [] - const existingQueries: DehydratedState['queries'] = [] - for (const dehydratedQuery of queries) { - const existingQuery = queryCache.get(dehydratedQuery.queryHash) + const newQueries: DehydratedState['queries'] = [] + const existingQueries: DehydratedState['queries'] = [] + for (const dehydratedQuery of state.queries) { + const existingQuery = queryCache.get(dehydratedQuery.queryHash) - if (!existingQuery) { - newQueries.push(dehydratedQuery) - } else { - const hydrationIsNewer = - dehydratedQuery.state.dataUpdatedAt > - existingQuery.state.dataUpdatedAt || - (dehydratedQuery.promise && - existingQuery.state.status !== 'pending' && - existingQuery.state.fetchStatus !== 'fetching' && - dehydratedQuery.dehydratedAt > existingQuery.state.dataUpdatedAt) + if (!existingQuery) { + newQueries.push(dehydratedQuery) + } else { + const hydrationIsNewer = + dehydratedQuery.state.dataUpdatedAt > + existingQuery.state.dataUpdatedAt || + (dehydratedQuery.promise && + existingQuery.state.status !== 'pending' && + existingQuery.state.fetchStatus !== 'fetching' && + dehydratedQuery.dehydratedAt > existingQuery.state.dataUpdatedAt) - if (hydrationIsNewer) { - existingQueries.push(dehydratedQuery) - } + if (hydrationIsNewer) { + existingQueries.push(dehydratedQuery) } } + } - if (newQueries.length > 0) { - // It's actually fine to call this with queries/state that already exists - // in the cache, or is older. hydrate() is idempotent for queries. - hydrate(client, { queries: newQueries }, optionsRef.current) - } - if (existingQueries.length > 0) { - return existingQueries - } + if (newQueries.length > 0) { + // It's actually fine to call this with queries/state that already exists + // in the cache, or is older. hydrate() is idempotent for queries. + hydrate(client, { queries: newQueries }, optionsRef.current) } - return undefined + return existingQueries.length > 0 ? existingQueries : undefined }, [client, state]) useEffect(() => { diff --git a/packages/preact-query/src/__tests__/HydrationBoundary.test.tsx b/packages/preact-query/src/__tests__/HydrationBoundary.test.tsx index 48802de96c..b0487a5b30 100644 --- a/packages/preact-query/src/__tests__/HydrationBoundary.test.tsx +++ b/packages/preact-query/src/__tests__/HydrationBoundary.test.tsx @@ -322,106 +322,6 @@ describe('Preact hydration', () => { }) }) - it('should not hydrate queries if state is null', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await Promise.all( - Array.from({ length: 1000 }).map(async (_, index) => { - await vi.advanceTimersByTimeAsync(index) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - }), - ) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - - it('should not hydrate queries if state is undefined', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await vi.advanceTimersByTimeAsync(0) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - - it('should not hydrate queries if state is not an object', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await vi.advanceTimersByTimeAsync(0) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - - it('should handle state without queries property gracefully', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await vi.advanceTimersByTimeAsync(0) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - // https://github.com/TanStack/query/issues/8677 it('should not infinite loop when hydrating promises that resolve to errors', async () => { const originalHydrate = coreModule.hydrate diff --git a/packages/react-query/src/HydrationBoundary.tsx b/packages/react-query/src/HydrationBoundary.tsx index ce03eb40c8..66387e9f4a 100644 --- a/packages/react-query/src/HydrationBoundary.tsx +++ b/packages/react-query/src/HydrationBoundary.tsx @@ -17,7 +17,7 @@ export interface HydrationBoundaryProps { /** * The state to hydrate. */ - state: DehydratedState | null | undefined + state: DehydratedState /** * Optional. Note: unlike `hydrate`, `mutations` cannot be set here. */ @@ -113,52 +113,37 @@ export const HydrationBoundary = ({ // updating the UI. const hydrationQueue: DehydratedState['queries'] | undefined = React.useMemo(() => { - if (state) { - if (typeof state !== 'object') { - return - } - - const queryCache = client.getQueryCache() - // State is supplied from the outside and we might as well fail - // gracefully if it has the wrong shape, so while we type `queries` - // as required, we still provide a fallback. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const queries = state.queries || [] + const queryCache = client.getQueryCache() - const newQueries: DehydratedState['queries'] = [] - const existingQueries: DehydratedState['queries'] = [] - for (const dehydratedQuery of queries) { - const existingQuery = queryCache.get(dehydratedQuery.queryHash) + const newQueries: DehydratedState['queries'] = [] + const existingQueries: DehydratedState['queries'] = [] + for (const dehydratedQuery of state.queries) { + const existingQuery = queryCache.get(dehydratedQuery.queryHash) - if (!existingQuery) { - newQueries.push(dehydratedQuery) - } else { - const hydrationIsNewer = - dehydratedQuery.state.dataUpdatedAt > - existingQuery.state.dataUpdatedAt || - (dehydratedQuery.promise && - existingQuery.state.status !== 'pending' && - existingQuery.state.fetchStatus !== 'fetching' && - dehydratedQuery.dehydratedAt > - existingQuery.state.dataUpdatedAt) + if (!existingQuery) { + newQueries.push(dehydratedQuery) + } else { + const hydrationIsNewer = + dehydratedQuery.state.dataUpdatedAt > + existingQuery.state.dataUpdatedAt || + (dehydratedQuery.promise && + existingQuery.state.status !== 'pending' && + existingQuery.state.fetchStatus !== 'fetching' && + dehydratedQuery.dehydratedAt > existingQuery.state.dataUpdatedAt) - if (hydrationIsNewer) { - existingQueries.push(dehydratedQuery) - } + if (hydrationIsNewer) { + existingQueries.push(dehydratedQuery) } } + } - if (newQueries.length > 0) { - // It's actually fine to call this with queries/state that already exists - // in the cache, or is older. hydrate() is idempotent for queries. - // eslint-disable-next-line react-hooks/refs - hydrate(client, { queries: newQueries }, optionsRef.current) - } - if (existingQueries.length > 0) { - return existingQueries - } + if (newQueries.length > 0) { + // It's actually fine to call this with queries/state that already exists + // in the cache, or is older. hydrate() is idempotent for queries. + // eslint-disable-next-line react-hooks/refs + hydrate(client, { queries: newQueries }, optionsRef.current) } - return undefined + return existingQueries.length > 0 ? existingQueries : undefined }, [client, state]) React.useEffect(() => { diff --git a/packages/react-query/src/__tests__/HydrationBoundary.test.tsx b/packages/react-query/src/__tests__/HydrationBoundary.test.tsx index 6418fb430f..ce8d809cc7 100644 --- a/packages/react-query/src/__tests__/HydrationBoundary.test.tsx +++ b/packages/react-query/src/__tests__/HydrationBoundary.test.tsx @@ -318,106 +318,6 @@ describe('React hydration', () => { }) }) - it('should not hydrate queries if state is null', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await Promise.all( - Array.from({ length: 1000 }).map(async (_, index) => { - await vi.advanceTimersByTimeAsync(index) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - }), - ) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - - it('should not hydrate queries if state is undefined', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await vi.advanceTimersByTimeAsync(0) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - - it('should not hydrate queries if state is not an object', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await vi.advanceTimersByTimeAsync(0) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - - it('should handle state without queries property gracefully', async () => { - const queryClient = new QueryClient() - - const hydrateSpy = vi.spyOn(coreModule, 'hydrate') - - function Page() { - return null - } - - render( - - - - - , - ) - - await vi.advanceTimersByTimeAsync(0) - expect(hydrateSpy).toHaveBeenCalledTimes(0) - - hydrateSpy.mockRestore() - queryClient.clear() - }) - // https://github.com/TanStack/query/issues/8677 it('should not infinite loop when hydrating promises that resolve to errors', async () => { const originalHydrate = coreModule.hydrate