Repository: tannerlinsley/react-query
Pull request #1763 (2021-02-08 11:49:27)

fixed typo

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.

🔍 Inspect: [https://vercel.com/tannerlinsley/react-query/fpnjz6q8n](https://vercel.com/tannerlinsley/react-query/fpnjz6q8n)  
✅ Preview: *In Progress*



Patch:
diff --git a/docs/src/pages/guides/ssr.md b/docs/src/pages/guides/ssr.md
index cfeeacc59..1e3ea5578 100644
--- a/docs/src/pages/guides/ssr.md
+++ b/docs/src/pages/guides/ssr.md
@@ -49,7 +49,7 @@ React Query supports prefetching multiple queries on the server in Next.js and t
 
 To support caching queries on the server and set up hydration:
 
-- Create a new `QueryClient` instance
+- Create a new `QueryClient` instance **inside of your app, and on an instance ref. This ensures that data is not shared between different users and requests.**
 - Wrap your app component with `<QueryClientProvider>` and pass it the client instance
 - Wrap your app component with `<Hydrate>` and pass it the `dehydratedState` prop from `pageProps`
 
@@ -58,11 +58,14 @@ To support caching queries on the server and set up hydration:
 import { QueryClient, QueryClientProvider } from 'react-query'
 import { Hydrate } from 'react-query/hydration'
 
-const queryClient = new QueryClient()
-
 export default function MyApp({ Component, pageProps }) {
+  const queryClientRef = React.useRef()
+  if (!queryClientRef.current) {
+    queryClientRef.current = new QueryClient()
+  }
+
   return (
-    <QueryClientProvider client={queryClient}>
+    <QueryClientProvider client={queryClientRef.current}>
       <Hydrate state={pageProps.dehydratedState}>
         <Component {...pageProps} />
       </Hydrate>
@@ -73,7 +76,7 @@ export default function MyApp({ Component, pageProps }) {
 
 Now you are ready to prefetch some data in your pages with either [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) (for SSG) or [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) (for SSR). From React Query's perspective, these integrate in the same way, `getStaticProps` is shown below.
 
-- Create a new `QueryClient` instance for each page request
+- Create a new `QueryClient` instance **for each page request. This ensures that data is not shared between users and requests.**
 - Prefetch the data using the clients `prefetchQuery` method and wait for it to complete
 - Use `dehydrate` to dehydrate the query cache and pass it to the page via the `dehydratedState` prop. This is the same prop that the cache will be picked up from in your `_app.js`
 
@@ -117,7 +120,7 @@ This guide is at-best, a high level overview of how SSR with React Query should
 
 ### On the Server
 
-- Create a new `QueryClient` instance
+- Create a new `QueryClient` instance **inside of your request handler. This ensures that data is not shared between different users and requests.**
 - Using the client, prefetch any data you need
 - Dehydrate the client
 - Render your app with the client provider and also **using the dehydrated state. This is extremely important! You must render both server and client using the same dehydrated state to ensure hydration on the client produces the exact same markup as the server.**
@@ -129,28 +132,30 @@ This guide is at-best, a high level overview of how SSR with React Query should
 import { QueryClient, QueryClientProvider } from 'react-query'
 import { dehydrate, Hydrate } from 'react-query/hydration'
 
-const queryClient = new QueryClient()
-await queryClient.prefetchQuery('key', fn)
-const dehydratedState = dehydrate(queryClient)
+function handleRequest (req, res) {
+  const queryClient = new QueryClient()
+  await queryClient.prefetchQuery('key', fn)
+  const dehydratedState = dehydrate(queryClient)
 
-const html = ReactDOM.renderToString(
-  <QueryClientProvider client={queryClient}>
-    <Hydrate state={dehydratedState}>
-      <App />
-    </Hydrate>
-  </QueryClientProvider>
-)
+  const html = ReactDOM.renderToString(
+    <QueryClientProvider client={queryClient}>
+      <Hydrate state={dehydratedState}>
+        <App />
+      </Hydrate>
+    </QueryClientProvider>
+  )
 
-res.send(`
-  <html>
-    <body>
-      <div id="root">${html}</div>
-      <script>
-        window.__REACT_QUERY_STATE__ = ${JSON.stringify(dehydratedState)};
-      </script>
-    </body>
-  </html>
+  res.send(`
+    <html>
+      <body>
+        <div id="root">${html}</div>
+        <script>
+          window.__REACT_QUERY_STATE__ = ${JSON.stringify(dehydratedState)};
+        </script>
+      </body>
+    </html>
   `)
+}
 ```
 
 ### Client
diff --git a/docs/src/pages/reference/QueryClient.md b/docs/src/pages/reference/QueryClient.md
index 5f86b4a9e..cd3d7e340 100644
--- a/docs/src/pages/reference/QueryClient.md
+++ b/docs/src/pages/reference/QueryClient.md
@@ -117,7 +117,7 @@ The options for `fetchInfiniteQuery` are exactly the same as those of [`fetchQue
 
 ## `queryClient.prefetchQuery`
 
-`prefetchQuery` is an asynchronous method that can be used to prefetch a query before it is needed or rendered with `useQuery` and friends. The method works the same as `fetchQuery` except that is will not throw or return any data.
+`prefetchQuery` is an asynchronous method that can be used to prefetch a query before it is needed or rendered with `useQuery` and friends. The method works the same as `fetchQuery` except that it will not throw or return any data.
 
 ```js
 await queryClient.prefetchQuery(queryKey, queryFn)
diff --git a/docs/src/pages/reference/useQuery.md b/docs/src/pages/reference/useQuery.md
index f68cc9209..b7ecab55c 100644
--- a/docs/src/pages/reference/useQuery.md
+++ b/docs/src/pages/reference/useQuery.md
@@ -43,6 +43,7 @@ const {
   refetchOnReconnect,
   refetchOnWindowFocus,
   retry,
+  retryOnMount,
   retryDelay,
   select,
   staleTime,
@@ -80,6 +81,8 @@ const result = useQuery({
   - If `false`, failed queries will not retry by default.
   - If `true`, failed queries will retry infinitely.
   - If set to an `number`, e.g. `3`, failed queries will retry until the failed query count meets that number.
+- `retryOnMount: boolean`
+  - If set to `false`, the query will not be retried on mount if it contains an error. Defaults to `true`.
 - `retryDelay: (retryAttempt: number) => number`
   - This function receives a `retryAttempt` integer and returns the delay to apply before the next attempt in milliseconds.
   - A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
diff --git a/examples/nextjs/pages/_app.js b/examples/nextjs/pages/_app.js
index cf2ca77ad..56b91717b 100644
--- a/examples/nextjs/pages/_app.js
+++ b/examples/nextjs/pages/_app.js
@@ -3,11 +3,14 @@ import { QueryClient, QueryClientProvider } from 'react-query'
 import { Hydrate } from 'react-query/hydration'
 import { ReactQueryDevtools } from 'react-query/devtools'
 
-const queryClient = new QueryClient()
-
 export default function MyApp({ Component, pageProps }) {
+  const queryClientRef = React.useRef()
+  if (!queryClientRef.current) {
+    queryClientRef.current = new QueryClient()
+  }
+
   return (
-    <QueryClientProvider client={queryClient}>
+    <QueryClientProvider client={queryClientRef.current}>
       <Hydrate state={pageProps.dehydratedState}>
         <Component {...pageProps} />
       </Hydrate>
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index 4c3518d15..6cbd29c92 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -21,6 +21,7 @@ import type { Query, QueryState, Action, FetchOptions } from './query'
 import type { QueryClient } from './queryClient'
 import { focusManager } from './focusManager'
 import { Subscribable } from './subscribable'
+import { getLogger } from './logger'
 
 type QueryObserverListener<TData, TError> = (
   result: QueryObserverResult<TData, TError>
@@ -240,6 +241,9 @@ export class QueryObserver<
         this.updateRefetchInterval()
       }
     }
+
+    // Reset previous options after all code related to option changes has run
+    this.previousOptions = this.options
   }
 
   getCurrentResult(): QueryObserverResult<TData, TError> {
@@ -390,6 +394,8 @@ export class QueryObserver<
     let isPlaceholderData = false
     let data: TData | undefined
     let dataUpdatedAt = state.dataUpdatedAt
+    let error = state.error
+    let errorUpdatedAt = state.errorUpdatedAt
 
     // Optimistically set status to loading if we will start fetching
     if (!this.hasListeners() && this.willFetchOnMount()) {
@@ -421,9 +427,16 @@ export class QueryObserver<
       ) {
         data = this.currentResult.data
       } else {
-        data = this.options.select(state.data)
-        if (this.options.structuralSharing !== false) {
-          data = replaceEqualDeep(this.currentResult?.data, data)
+        try {
+          data = this.options.select(state.data)
+          if (this.options.structuralSharing !== false) {
+            data = replaceEqualDeep(this.currentResult?.data, data)
+          }
+        } catch (selectError) {
+          getLogger().error(selectError)
+          error = selectError
+          errorUpdatedAt = Date.now()
+          status = 'error'
         }
       }
     }
@@ -453,8 +466,8 @@ export class QueryObserver<
       ...getStatusProps(status),
       data,
       dataUpdatedAt,
-      error: state.error,
-      errorUpdatedAt: state.errorUpdatedAt,
+      error,
+      errorUpdatedAt,
       failureCount: state.fetchFailureCount,
       isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0,
       isFetchedAfterMount:


Test patch:
diff --git a/.github/workflows/test-and-publish.yml b/.github/workflows/test-and-publish.yml
index 958b97ced..d8ef950e2 100644
--- a/.github/workflows/test-and-publish.yml
+++ b/.github/workflows/test-and-publish.yml
@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - 'master'
+      - 'next'
       - 'beta'
       - '1.x'
       - '2.x'
@@ -29,7 +30,7 @@ jobs:
     name: 'Publish Module to NPM'
     needs: test
     # publish only when merged in master on original repo, not on PR
-    if: github.repository == 'tannerlinsley/react-query' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/1.x' || github.ref == 'refs/heads/2.x')
+    if: github.repository == 'tannerlinsley/react-query' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/next' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/1.x' || github.ref == 'refs/heads/2.x')
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
diff --git a/src/core/tests/queryObserver.test.tsx b/src/core/tests/queryObserver.test.tsx
index 8ffa73cb6..91dcbc673 100644
--- a/src/core/tests/queryObserver.test.tsx
+++ b/src/core/tests/queryObserver.test.tsx
@@ -158,11 +158,30 @@ describe('queryObserver', () => {
       select: select2,
     })
     await sleep(1)
+    await observer.refetch()
     unsubscribe()
     expect(count).toBe(2)
-    expect(results.length).toBe(2)
-    expect(results[0]).toMatchObject({ data: { myCount: 1 } })
-    expect(results[1]).toMatchObject({ data: { myCount: 99 } })
+    expect(results.length).toBe(4)
+    expect(results[0]).toMatchObject({
+      status: 'success',
+      isFetching: false,
+      data: { myCount: 1 },
+    })
+    expect(results[1]).toMatchObject({
+      status: 'success',
+      isFetching: false,
+      data: { myCount: 99 },
+    })
+    expect(results[2]).toMatchObject({
+      status: 'success',
+      isFetching: true,
+      data: { myCount: 99 },
+    })
+    expect(results[3]).toMatchObject({
+      status: 'success',
+      isFetching: false,
+      data: { myCount: 99 },
+    })
   })
 
   test('should not run the selector again if the data and selector did not change', async () => {
@@ -189,10 +208,25 @@ describe('queryObserver', () => {
       select,
     })
     await sleep(1)
+    await observer.refetch()
     unsubscribe()
     expect(count).toBe(1)
-    expect(results.length).toBe(1)
-    expect(results[0]).toMatchObject({ data: { myCount: 1 } })
+    expect(results.length).toBe(3)
+    expect(results[0]).toMatchObject({
+      status: 'success',
+      isFetching: false,
+      data: { myCount: 1 },
+    })
+    expect(results[1]).toMatchObject({
+      status: 'success',
+      isFetching: true,
+      data: { myCount: 1 },
+    })
+    expect(results[2]).toMatchObject({
+      status: 'success',
+      isFetching: false,
+      data: { myCount: 1 },
+    })
   })
 
   test('should not run the selector again if the data did not change', async () => {
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 8ac67e533..9dac880bb 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -729,6 +729,35 @@ describe('useQuery', () => {
     expect(states[1]).toMatchObject({ data: 'test' })
   })
 
+  it('should throw an error when a selector throws', async () => {
+    const consoleMock = mockConsoleError()
+    const key = queryKey()
+    const states: UseQueryResult<string>[] = []
+    const error = new Error('Select Error')
+
+    function Page() {
+      const state = useQuery(key, () => ({ name: 'test' }), {
+        select: () => {
+          throw error
+        },
+      })
+      states.push(state)
+      return null
+    }
+
+    renderWithClient(queryClient, <Page />)
+
+    await sleep(10)
+
+    expect(consoleMock).toHaveBeenCalledWith(error)
+    expect(states.length).toBe(2)
+
+    expect(states[0]).toMatchObject({ status: 'loading', data: undefined })
+    expect(states[1]).toMatchObject({ status: 'error', error })
+
+    consoleMock.mockRestore()
+  })
+
   it('should re-render when dataUpdatedAt changes but data remains the same', async () => {
     const key = queryKey()
     const states: UseQueryResult<string>[] = []