Repository: tannerlinsley/react-query
Pull request #168 (2020-02-27 20:44:23)

Make sure query is refetched specified number of times
If `retry` is set to 1, I would expect `useQuery` to try once again after fail

cherniavskii: That's weird, tests are passing on my machine :/
tannerlinsley: Hmm Travis env issue maybe?
cherniavskii: Can you check on your machine?
tannerlinsley: Passes 😂 
cherniavskii: Hey Travis, WTF?
tannerlinsley: Wait. I got it to fail locally. Try this:

```js
const rendered = render(<Page />)

    await waitForElement(() => rendered.getByText('error'))

    rendered.debug()

    // query should fail `retry + 1` times, since first time isn't a "retry"
    await waitForElement(() => rendered.getByText('Failed 2 times'))

    rendered.debug()
```
tannerlinsley: Looks like it never flushes the `error` state because the delay is so small? Thoughts?
cherniavskii: To minimize the delay i've set `retryDelay` to 1.
Will check if it works
tannerlinsley: Oooohhhh, I don't think the `error` status is reached until the very end. It stays in the loading state until the final failure or success.
tannerlinsley: And I am pretty sure that is desired from the UI/UX standpoint out of the box.
cherniavskii: Yeah, this is fine
tannerlinsley: Something still doesn't seem right tho. Try logging out the failurCount in the render function....
tannerlinsley: Oh wait. I forgot to build. I think it's good.  This is what I have now:

```js
it('should retry specified number of times', async () => {
    const queryFn = jest.fn()
    queryFn.mockImplementation(() => {
      return Promise.reject('Error test')
    })

    function Page() {
      const { status, failureCount } = useQuery('test', queryFn, {
        retry: 1,
        retryDelay: 1,
      })

      console.log(failureCount)

      return (
        <div>
          <h1>{status}</h1>
          <h2>Failed {failureCount} times</h2>
        </div>
      )
    }

    const rendered = render(<Page />)

    rendered.getByText('loading')

    // query should fail `retry + 1` times, since first time isn't a "retry"
    await waitForElement(() => rendered.getByText('Failed 2 times'))

    rendered.debug()

    expect(queryFn).toHaveBeenCalledTimes(2)
  })
```
cherniavskii: Let's try
cherniavskii: Wow, I had to set 50s timeout to make it pass :D

I don't have a better solution for now, at least it works
tannerlinsley: That just doesn't seem right though... like something else is wrong.
tannerlinsley: Have you tested this in the browser and played with it there?
cherniavskii: Yeah https://codesandbox.io/s/spring-tdd-lzlj6
cherniavskii: Same with tests - they're passing really fast locally.
That single test takes 4.587s
tannerlinsley: That seems really strange. I want to get to the bottom of it.
tannerlinsley: I think I found the reason. `queryCache` is shared among test instances, which are all running in parallel I think...
tannerlinsley: Well, no, they're in isolated workers.... What is going on!!!
iamchanii: I'd experienced the issue what almost same it. the test passed on my machine but CI failed by timeout. so I had to google and found a solution.

if this project use Jest, would you like to try `--maxWorkers=2` flag? it is working for my case without increase timeout seconds. but there is a slight drop in performance.
cherniavskii: @iamchanii Unfortunately that didn't help - https://github.com/tannerlinsley/react-query/pull/168/commits/9f46b36d0e0a2f833f805f5f355e0cfef1c6c6d9
cherniavskii: > queryCache is shared among test instances

I think this is true, looking into this...

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc46c982c..d6d1e761a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 1.0.6
+
+- Fixed an issue where paginated queries, when updated rapidly, would display an unnecessary loading state.
+
 ## 1.0.5
 
 - Fixed a regression where query errors were not thrown properly
diff --git a/examples/sandbox/src/index.app.js b/examples/sandbox/src/index.app.js
index 3ca3632d3..f0c3ffd3a 100644
--- a/examples/sandbox/src/index.app.js
+++ b/examples/sandbox/src/index.app.js
@@ -62,7 +62,7 @@ let queryTimeMin = 1000;
 let queryTimeMax = 2000;
 
 const fetchTodos = (key, { filter } = {}) => {
-  console.log("fetchTodos", { filter });
+  console.info("fetchTodos", { filter });
   const promise = new Promise((resolve, reject) => {
     setTimeout(() => {
       if (Math.random() < errorRate) {
@@ -74,13 +74,13 @@ const fetchTodos = (key, { filter } = {}) => {
     }, queryTimeMin + Math.random() * (queryTimeMax - queryTimeMin));
   });
 
-  promise.cancel = () => console.log("cancelled", filter);
+  promise.cancel = () => console.info("cancelled", filter);
 
   return promise;
 };
 
 const fetchTodoById = (key, { id }) => {
-  console.log("fetchTodoById", { id });
+  console.info("fetchTodoById", { id });
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       if (Math.random() < errorRate) {
@@ -94,7 +94,7 @@ const fetchTodoById = (key, { id }) => {
 };
 
 const postTodo = ({ name, notes }) => {
-  console.log("postTodo", { name, notes });
+  console.info("postTodo", { name, notes });
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       if (Math.random() < errorRate) {
@@ -110,7 +110,7 @@ const postTodo = ({ name, notes }) => {
 };
 
 const patchTodo = todo => {
-  console.log("patchTodo", todo);
+  console.info("patchTodo", todo);
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       if (Math.random() < errorRate) {
diff --git a/examples/suspense/src/queries.js b/examples/suspense/src/queries.js
index f47b66a58..1afc8fdc1 100644
--- a/examples/suspense/src/queries.js
+++ b/examples/suspense/src/queries.js
@@ -1,7 +1,7 @@
 import axios from "axios";
 
 export async function fetchProjects(key) {
-  console.log("fetch projects");
+  console.info("fetch projects");
   let { data } = await axios.get(
     `https://api.github.com/users/tannerlinsley/repos?sort=updated`
   );
@@ -10,7 +10,7 @@ export async function fetchProjects(key) {
 }
 
 export async function fetchProject(key, { id }) {
-  console.log("fetch project id", id);
+  console.info("fetch project id", id);
   let { data } = await axios.get(
     `https://api.github.com/repos/tannerlinsley/${id}`
   );
diff --git a/package.json b/package.json
index a20ea4fd5..77b667461 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-query",
-  "version": "1.0.5",
+  "version": "1.0.6",
   "description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
   "author": "tannerlinsley",
   "license": "MIT",
diff --git a/src/queryCache.js b/src/queryCache.js
index da4ed8185..810c68fd9 100644
--- a/src/queryCache.js
+++ b/src/queryCache.js
@@ -206,7 +206,7 @@ export function makeQueryCache() {
         () => {
           cache.removeQueries(d => d.queryHash === query.queryHash)
         },
-        typeof query.state.data === 'undefined' ? 0 : query.config.cacheTime
+        (typeof query.state.data === 'undefined' && query.state.status !== 'error') ? 0 : query.config.cacheTime
       )
     }
 
@@ -276,7 +276,7 @@ export function makeQueryCache() {
         if (
           // Only retry if the document is visible
           query.config.retry === true ||
-          query.state.failureCount < query.config.retry
+          query.state.failureCount <= query.config.retry
         ) {
           if (!isDocumentVisible()) {
             return new Promise(noop)
diff --git a/src/usePaginatedQuery.js b/src/usePaginatedQuery.js
index 9efc55b92..17e24f43a 100644
--- a/src/usePaginatedQuery.js
+++ b/src/usePaginatedQuery.js
@@ -20,7 +20,7 @@ export function usePaginatedQuery(...args) {
   let { data: latestData, status } = queryInfo
 
   React.useEffect(() => {
-    if (status === 'success') {
+    if (status === 'success' && typeof latestData !== 'undefined') {
       lastDataRef.current = latestData
     }
   }, [latestData, status])


Test patch:
diff --git a/src/tests/useQuery.test.js b/src/tests/useQuery.test.js
index 1358fae12..f76682aec 100644
--- a/src/tests/useQuery.test.js
+++ b/src/tests/useQuery.test.js
@@ -165,7 +165,7 @@ describe('useQuery', () => {
         () => {
           return Promise.reject('Error test')
         },
-        { retry: false },
+        { retry: false }
       )
 
       return (
@@ -179,4 +179,35 @@ describe('useQuery', () => {
 
     await waitForElement(() => rendered.getByText('error'))
   })
+
+  it('should retry specified number of times', async () => {
+    const queryFn = jest.fn()
+    queryFn.mockImplementation(() => {
+      return Promise.reject('Error test')
+    })
+
+    function Page() {
+      const { status, failureCount } = useQuery('test', queryFn, {
+        retry: 1,
+        retryDelay: 1,
+      })
+
+      return (
+        <div>
+          <h1>{status}</h1>
+          <h2>Failed {failureCount} times</h2>
+        </div>
+      )
+    }
+
+    const rendered = render(<Page />)
+
+    await waitForElement(() => rendered.getByText('loading'))
+    await waitForElement(() => rendered.getByText('error'))
+
+    // query should fail `retry + 1` times, since first time isn't a "retry"
+    await waitForElement(() => rendered.getByText('Failed 2 times'))
+
+    expect(queryFn).toHaveBeenCalledTimes(2)
+  })
 })