Repository: tannerlinsley/react-query
Pull request #943 (2020-08-31 18:07:14)

feat: remove ReactQueryCacheProvider from hydration
This MR removes the `hydration/ReactQueryCacheProvider` component and replaces it with a `Hydrate` component. The `hydration/ReactQueryCacheProvider` component has some mixed responsibilities and having two of them could be a bit confusing. Exposing a `Hydrate` component simplifies the API because we only export three things which all basically do the same thing: `hydrate()`, `<Hydrate />` and `useHydrate()`. @Ephem 

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.


Ephem: Hey! I wont have time to really review this PR because of personal stuff happening, but I'm absolutely behind the idea. `<Hydrate />` was my first choice for the component API as well and I still prefer that to what got merged in the end.

Patch:
diff --git a/docs/src/pages/docs/api.md b/docs/src/pages/docs/api.md
index 8e6353187..595350c4e 100644
--- a/docs/src/pages/docs/api.md
+++ b/docs/src/pages/docs/api.md
@@ -792,13 +792,13 @@ setConsole({
 
 ## `hydration/dehydrate`
 
-`dehydrate` creates a frozen representation of a `queryCache` that can later be hydrated with `useHydrate`, `hydrate` or by passing it into `hydration/ReactQueryCacheProvider`. This is useful for passing prefetched queries from server to client or persisting queries to localstorage. It only includes currently successful queries by default.
+`dehydrate` creates a frozen representation of a `queryCache` that can later be hydrated with `useHydrate`, `hydrate` or `Hydrate`. This is useful for passing prefetched queries from server to client or persisting queries to localstorage. It only includes currently successful queries by default.
 
 ```js
 import { dehydrate } from 'react-query/hydration'
 
 const dehydratedState = dehydrate(queryCache, {
-  shouldDehydrate
+  shouldDehydrate,
 })
 ```
 
@@ -824,7 +824,7 @@ const dehydratedState = dehydrate(queryCache, {
 `hydrate` adds a previously dehydrated state into a `queryCache`. If the queries included in dehydration already exist in the cache, `hydrate` does not overwrite them.
 
 ```js
-import { hydrate } from 'react-query/hydration'
+import { hydrate } from 'react-query/hydration'
 
 hydrate(queryCache, dehydratedState)
 ```
@@ -854,31 +854,19 @@ useHydrate(dehydratedState)
   - **Required**
   - The state to hydrate
 
-## `hydration/ReactQueryCacheProvider`
+## `hydration/Hydrate`
 
-`hydration/ReactQueryCacheProvider` does the same thing as `ReactQueryCacheProvider` but also supports hydrating an initial state into the cache.
+`hydration/Hydrate` does the same thing as `useHydrate` but exposed as a component.
 
 ```js
-import { ReactQueryCacheProvider } from 'react-query/hydration'
+import { Hydrate } from 'react-query/hydration'
 
 function App() {
-  return (
-    <ReactQueryCacheProvider
-      queryCache={queryCache}
-      dehydratedState={dehydratedState}
-      hydrationConfig={hydrationConfig}>
-      ...
-    </ReactQueryCacheProvider>
-  )
+  return <Hydrate state={dehydratedState}>...</Hydrate>
 }
 ```
 
 **Options**
 
-- `queryCache: QueryCache`
-  - In instance of queryCache, you can use the `makeQueryCache` factory to create this.
-  - If not provided, a new cache will be generated.
-- `dehydratedState: DehydratedState`
+- `state: DehydratedState`
   - The state to hydrate
-- `hydrationConfig`
-  - Same config as for `hydrate`
diff --git a/docs/src/pages/docs/guides/ssr.md b/docs/src/pages/docs/guides/ssr.md
index cc1a30203..338ee2eb6 100644
--- a/docs/src/pages/docs/guides/ssr.md
+++ b/docs/src/pages/docs/guides/ssr.md
@@ -13,11 +13,11 @@ This approach works well for applications or user-specific pages that might cont
 
 React Query supports two ways of prefetching data on the server and passing that to the client.
 
-* Prefetch the data yourself and pass it in as `initialData`
-  * Quick to set up for simple cases
-  * Has some caveats
-* Prefetch the query via React Query and use de/rehydration
-  * Requires slightly more setup up front
+- Prefetch the data yourself and pass it in as `initialData`
+  - Quick to set up for simple cases
+  - Has some caveats
+- Prefetch the query via React Query and use de/rehydration
+  - Requires slightly more setup up front
 
 The exact implementation of these mechanisms may vary from platform to platform, but we recommend starting with Next.js which supports [2 forms of pre-rendering](https://nextjs.org/docs/basic-features/data-fetching):
 
@@ -45,9 +45,9 @@ function Posts(props) {
 
 The setup is minimal and this can be a perfect solution for some cases, but there are a few tradeoffs compared to the full approach:
 
-* If you are calling `useQuery` in a component deeper down in the tree you need to pass the `initialData` down to that point
-* If you are calling `useQuery` with the same query in multiple locations, you need to pass `initialData` to all of them
-* There is no way to know at what time the query was fetched on the server, so `updatedAt` and determining if the query needs refetching is based on when the page loaded instead
+- If you are calling `useQuery` in a component deeper down in the tree you need to pass the `initialData` down to that point
+- If you are calling `useQuery` with the same query in multiple locations, you need to pass `initialData` to all of them
+- There is no way to know at what time the query was fetched on the server, so `updatedAt` and determining if the query needs refetching is based on when the page loaded instead
 
 ## Prefetch the query via React Query and use de/rehydration
 
@@ -55,18 +55,19 @@ React Query supports prefetching a query on the server and handing off or _dehyd
 
 ### Integrating with Next.js
 
-To support caching queries on the server and set up hydration, you start with wrapping your application with `<ReactQueryCacheProvider>` in `_app.js`.
-
-> Note: You need to import `ReactQueryCacheProvider` from `'react-query/hydration'` for it to support hydration!
+To support caching queries on the server and set up hydration, you start with wrapping your application with `<ReactQueryCacheProvider>` and `<Hydrate>` in `_app.js`.
 
 ```jsx
 // _app.jsx
-import { ReactQueryCacheProvider } from 'react-query/hydration'
+import { ReactQueryCacheProvider } from 'react-query'
+import { Hydrate } from 'react-query/hydration'
 
 export default function MyApp({ Component, pageProps }) {
   return (
-    <ReactQueryCacheProvider dehydratedState={pageProps.dehydratedState}>
-      <Component {...pageProps} />
+    <ReactQueryCacheProvider>
+      <Hydrate state={pageProps.dehydratedState}>
+        <Component {...pageProps} />
+      </Hydrate>
     </ReactQueryCacheProvider>
   )
 }
@@ -86,8 +87,8 @@ export async function getStaticProps() {
 
   return {
     props: {
-      dehydratedState: dehydrate(queryCache)
-    }
+      dehydratedState: dehydrate(queryCache),
+    },
   }
 }
 
@@ -119,7 +120,7 @@ Since there are many different possible setups for SSR, it's hard to give a deta
   - Call `prefetchQueryCache.prefetchQuery(...)` to prefetch queries
   - Dehydrate by using `const dehydratedState = dehydrate(prefetchQueryCache)`
 - Render
-  - Wrap the app in `<ReactQueryCacheProvider>` from `'react-query/hydration'` and pass in `dehydratedState`
+  - Wrap the app in `<ReactQueryCacheProvider>` and `<Hydrate>` to create a new cache and hydrate the state
     - This makes sure a separate `queryCache` is created specifically for rendering
     - **Do not** pass in the `prefetchQueryCache` from the last step, the server and client both needs to render from the dehydrated data to avoid React hydration mismatches. This is because queries with errors are excluded from dehydration by default.
 - Serialize and embed `dehydratedState` in the markup
@@ -129,7 +130,7 @@ Since there are many different possible setups for SSR, it's hard to give a deta
 
 - Parse `dehydratedState` from where you put it in the markup
 - Render
-  - Wrap the app in `<ReactQueryCacheProvider>` from `'react-query/hydration'` and pass in `dehydratedState`
+  - Wrap the app in `<Hydrate>` from `'react-query/hydration'` and pass in the dehyrated state.
 
 This list aims to be exhaustive, but depending on your current setup, the above steps can take more or less work. Here is a barebones example:
 
@@ -140,15 +141,19 @@ await prefetchCache.prefetchQuery('key', fn)
 const dehydratedState = dehydrate(prefetchCache)
 
 const html = ReactDOM.renderToString(
-  <ReactQueryCacheProvider dehydratedState={dehydratedState}>
-    <App />
+  <ReactQueryCacheProvider>
+    <Hydrate state={dehyratedState}>
+      <App />
+    </Hydrate>
   </ReactQueryCacheProvider>
 )
 res.send(`
 <html>
   <body>
     <div id="app">${html}</div>
-    <script>window.__REACT_QUERY_INITIAL_QUERIES__ = ${JSON.stringify(dehydratedState)};</script>
+    <script>window.__REACT_QUERY_INITIAL_QUERIES__ = ${JSON.stringify(
+      dehydratedState
+    )};</script>
   </body>
 </html>
 `)
@@ -156,8 +161,10 @@ res.send(`
 // Client
 const dehydratedState = JSON.parse(window.__REACT_QUERY_INITIAL_QUERIES__)
 ReactDOM.hydrate(
-  <ReactQueryCacheProvider dehydratedState={dehydratedState}>
-    <App />
+  <ReactQueryCacheProvider>
+    <Hydrate state={dehyratedState}>
+      <App />
+    </Hydrate>
   </ReactQueryCacheProvider>
 )
 ```
diff --git a/package.json b/package.json
index d80fb026c..93825fa3a 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,8 @@
   "files": [
     "dist",
     "types",
-    "scripts"
+    "scripts",
+    "hydration.js"
   ],
   "dependencies": {},
   "peerDependencies": {
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index 505308ed0..1180c27a1 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -277,9 +277,10 @@ export class QueryObserver<TResult, TError> {
 
     // Remove the initial data when there is an existing query
     // because this data should not be used for a new query
-    const config = prevQuery
-      ? { ...this.config, initialData: undefined }
-      : this.config
+    const config =
+      this.config.keepPreviousData && prevQuery
+        ? { ...this.config, initialData: undefined }
+        : this.config
 
     const newQuery = config.queryCache!.buildQuery(config.queryKey, config)
 
diff --git a/src/hydration/index.ts b/src/hydration/index.ts
index 183124753..7e387c506 100644
--- a/src/hydration/index.ts
+++ b/src/hydration/index.ts
@@ -1,5 +1,5 @@
 export { dehydrate, hydrate } from './hydration'
-export { useHydrate, ReactQueryCacheProvider } from './react'
+export { useHydrate, Hydrate } from './react'
 
 // Types
 export type {
@@ -9,4 +9,4 @@ export type {
   ShouldDehydrateFunction,
   DehydrateConfig,
 } from './hydration'
-export type { HydrationCacheProviderProps } from './react'
+export type { HydrateProps } from './react'
diff --git a/src/hydration/react.tsx b/src/hydration/react.tsx
index f8f60b449..2a0339b04 100644
--- a/src/hydration/react.tsx
+++ b/src/hydration/react.tsx
@@ -1,11 +1,7 @@
 import React from 'react'
-import {
-  useQueryCache,
-  ReactQueryCacheProvider as CacheProvider,
-} from 'react-query'
-import { hydrate } from './hydration'
+import { useQueryCache } from 'react-query'
 
-import type { ReactQueryCacheProviderProps } from '../react'
+import { hydrate } from './hydration'
 
 export function useHydrate(queries: unknown) {
   const queryCache = useQueryCache()
@@ -21,28 +17,11 @@ export function useHydrate(queries: unknown) {
   }, [queryCache, queries])
 }
 
-interface HydratorProps {
-  dehydratedState?: unknown
+export interface HydrateProps {
+  state?: unknown
 }
 
-const Hydrator: React.FC<HydratorProps> = ({ dehydratedState, children }) => {
-  useHydrate(dehydratedState)
+export const Hydrate: React.FC<HydrateProps> = ({ state, children }) => {
+  useHydrate(state)
   return children as React.ReactElement<any>
 }
-
-export interface HydrationCacheProviderProps
-  extends ReactQueryCacheProviderProps {
-  dehydratedState?: unknown
-}
-
-export const ReactQueryCacheProvider: React.FC<HydrationCacheProviderProps> = ({
-  dehydratedState,
-  children,
-  ...rest
-}) => {
-  return (
-    <CacheProvider {...rest}>
-      <Hydrator dehydratedState={dehydratedState}>{children}</Hydrator>
-    </CacheProvider>
-  )
-}


Test patch:
diff --git a/src/hydration/tests/react.test.tsx b/src/hydration/tests/react.test.tsx
index 21945693c..ba2a319de 100644
--- a/src/hydration/tests/react.test.tsx
+++ b/src/hydration/tests/react.test.tsx
@@ -1,12 +1,8 @@
 import React from 'react'
 import { render } from '@testing-library/react'
 
-import {
-  ReactQueryCacheProvider as OriginalCacheProvider,
-  makeQueryCache,
-  useQuery,
-} from '../..'
-import { dehydrate, useHydrate, ReactQueryCacheProvider } from '../'
+import { ReactQueryCacheProvider, makeQueryCache, useQuery } from '../..'
+import { dehydrate, useHydrate, Hydrate } from '../'
 import { waitForMs } from '../../react/tests/utils'
 
 describe('React hydration', () => {
@@ -58,9 +54,9 @@ describe('React hydration', () => {
       }
 
       const rendered = render(
-        <OriginalCacheProvider queryCache={clientQueryCache}>
+        <ReactQueryCacheProvider queryCache={clientQueryCache}>
           <Page />
-        </OriginalCacheProvider>
+        </ReactQueryCacheProvider>
       )
 
       await waitForMs(10)
@@ -84,11 +80,10 @@ describe('React hydration', () => {
       }
 
       const rendered = render(
-        <ReactQueryCacheProvider
-          queryCache={clientQueryCache}
-          dehydratedState={dehydratedState}
-        >
-          <Page queryKey={'string'} />
+        <ReactQueryCacheProvider queryCache={clientQueryCache}>
+          <Hydrate state={dehydratedState}>
+            <Page queryKey={'string'} />
+          </Hydrate>
         </ReactQueryCacheProvider>
       )
 
@@ -104,12 +99,11 @@ describe('React hydration', () => {
       intermediateCache.clear({ notify: false })
 
       rendered.rerender(
-        <ReactQueryCacheProvider
-          queryCache={clientQueryCache}
-          dehydratedState={dehydrated}
-        >
-          <Page queryKey={'string'} />
-          <Page queryKey={'added string'} />
+        <ReactQueryCacheProvider queryCache={clientQueryCache}>
+          <Hydrate state={dehydrated}>
+            <Page queryKey={'string'} />
+            <Page queryKey={'added string'} />
+          </Hydrate>
         </ReactQueryCacheProvider>
       )
 
@@ -137,11 +131,10 @@ describe('React hydration', () => {
       }
 
       const rendered = render(
-        <ReactQueryCacheProvider
-          queryCache={clientQueryCache}
-          dehydratedState={dehydratedState}
-        >
-          <Page />
+        <ReactQueryCacheProvider queryCache={clientQueryCache}>
+          <Hydrate state={dehydratedState}>
+            <Page />
+          </Hydrate>
         </ReactQueryCacheProvider>
       )
 
@@ -151,11 +144,10 @@ describe('React hydration', () => {
       const newClientQueryCache = makeQueryCache()
 
       rendered.rerender(
-        <ReactQueryCacheProvider
-          queryCache={newClientQueryCache}
-          dehydratedState={dehydratedState}
-        >
-          <Page />
+        <ReactQueryCacheProvider queryCache={newClientQueryCache}>
+          <Hydrate state={dehydratedState}>
+            <Page />
+          </Hydrate>
         </ReactQueryCacheProvider>
       )
 
diff --git a/src/hydration/tests/ssr.test.tsx b/src/hydration/tests/ssr.test.tsx
index e1f5a82f6..5d83153dc 100644
--- a/src/hydration/tests/ssr.test.tsx
+++ b/src/hydration/tests/ssr.test.tsx
@@ -2,8 +2,14 @@ import * as React from 'react'
 import ReactDOM from 'react-dom'
 import ReactDOMServer from 'react-dom/server'
 import { waitFor } from '@testing-library/react'
-import { makeQueryCache, useQuery, setConsole } from '../..'
-import { dehydrate, ReactQueryCacheProvider } from '../'
+
+import {
+  makeQueryCache,
+  useQuery,
+  setConsole,
+  ReactQueryCacheProvider,
+} from '../..'
+import { dehydrate, Hydrate } from '../'
 import * as utils from '../../core/utils'
 import { sleep } from '../../react/tests/utils'
 
@@ -73,8 +79,10 @@ describe('Server side rendering with de/rehydration', () => {
     await prefetchPromise
     const dehydratedStateServer = dehydrate(serverPrefetchCache)
     const markup = ReactDOMServer.renderToString(
-      <ReactQueryCacheProvider dehydratedState={dehydratedStateServer}>
-        <SuccessComponent />
+      <ReactQueryCacheProvider>
+        <Hydrate state={dehydratedStateServer}>
+          <SuccessComponent />
+        </Hydrate>
       </ReactQueryCacheProvider>
     )
     const stringifiedState = JSON.stringify(dehydratedStateServer)
@@ -87,8 +95,10 @@ describe('Server side rendering with de/rehydration', () => {
     el.innerHTML = markup
     const dehydratedStateClient = JSON.parse(stringifiedState)
     ReactDOM.hydrate(
-      <ReactQueryCacheProvider dehydratedState={dehydratedStateClient}>
-        <SuccessComponent />
+      <ReactQueryCacheProvider>
+        <Hydrate state={dehydratedStateClient}>
+          <SuccessComponent />
+        </Hydrate>
       </ReactQueryCacheProvider>,
       el
     )
@@ -139,8 +149,10 @@ describe('Server side rendering with de/rehydration', () => {
     await prefetchPromise
     const dehydratedStateServer = dehydrate(serverQueryCache)
     const markup = ReactDOMServer.renderToString(
-      <ReactQueryCacheProvider dehydratedState={dehydratedStateServer}>
-        <ErrorComponent />
+      <ReactQueryCacheProvider>
+        <Hydrate state={dehydratedStateServer}>
+          <ErrorComponent />
+        </Hydrate>
       </ReactQueryCacheProvider>
     )
     const stringifiedState = JSON.stringify(dehydratedStateServer)
@@ -153,8 +165,10 @@ describe('Server side rendering with de/rehydration', () => {
     el.innerHTML = markup
     const dehydratedStateClient = JSON.parse(stringifiedState)
     ReactDOM.hydrate(
-      <ReactQueryCacheProvider dehydratedState={dehydratedStateClient}>
-        <ErrorComponent />
+      <ReactQueryCacheProvider>
+        <Hydrate state={dehydratedStateClient}>
+          <ErrorComponent />
+        </Hydrate>
       </ReactQueryCacheProvider>,
       el
     )
@@ -204,8 +218,10 @@ describe('Server side rendering with de/rehydration', () => {
     const serverPrefetchCache = makeQueryCache()
     const dehydratedStateServer = dehydrate(serverPrefetchCache)
     const markup = ReactDOMServer.renderToString(
-      <ReactQueryCacheProvider dehydratedState={dehydratedStateServer}>
-        <SuccessComponent />
+      <ReactQueryCacheProvider>
+        <Hydrate state={dehydratedStateServer}>
+          <SuccessComponent />
+        </Hydrate>
       </ReactQueryCacheProvider>
     )
     const stringifiedState = JSON.stringify(dehydratedStateServer)
@@ -218,8 +234,10 @@ describe('Server side rendering with de/rehydration', () => {
     el.innerHTML = markup
     const dehydratedStateClient = JSON.parse(stringifiedState)
     ReactDOM.hydrate(
-      <ReactQueryCacheProvider dehydratedState={dehydratedStateClient}>
-        <SuccessComponent />
+      <ReactQueryCacheProvider>
+        <Hydrate state={dehydratedStateClient}>
+          <SuccessComponent />
+        </Hydrate>
       </ReactQueryCacheProvider>,
       el
     )
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 2307b4efe..e30206c76 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -698,6 +698,38 @@ describe('useQuery', () => {
     consoleMock.mockRestore()
   })
 
+  it('should keep initial stale and initial data when the query key changes', async () => {
+    const key = queryKey()
+    const states: QueryResult<{ count: number }>[] = []
+
+    function Page() {
+      const [count, setCount] = React.useState(0)
+      const state = useQuery([key, count], () => ({ count: 10 }), {
+        initialStale: () => false,
+        initialData: () => ({ count }),
+      })
+      states.push(state)
+
+      React.useEffect(() => {
+        setTimeout(() => setCount(1))
+      }, [])
+
+      return null
+    }
+
+    render(<Page />)
+
+    await waitFor(() => expect(states.length).toBe(5))
+
+    expect(states).toMatchObject([
+      { data: { count: 0 } },
+      { data: { count: 0 } },
+      { data: { count: 1 } },
+      { data: { count: 1 } },
+      { data: { count: 10 } },
+    ])
+  })
+
   it('should retry specified number of times', async () => {
     const key = queryKey()
     const consoleMock = mockConsoleError()