Repository: tannerlinsley/react-query
Pull request #1556 (2021-01-02 16:46:13)

fix: :bug: Make sure queries which have dataUpdatedAt of 0 will be run
When initialDataUpdatedAt is a falsy numeric value (e.g. 0), it will cause it to be replaced with Date.now(). This leads to unexpected behaviour, as one would expect the query to be invalidated instantly, if staleTime is set to an amount less than the result of Date.now(). This change will check if the value is numeric, and if so, always use this numeric value.

BREAKING: This changes the behaviour of the plugin for those users that have specified initialDataUpdated at <= 0.

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/k01p0iiut](https://vercel.com/tannerlinsley/react-query/k01p0iiut)  
✅ Preview: *In Progress*


meesvandongen: The problem is that a valid timestamp, 0 (number, 1970) is falsy, and
therefore gets converted to date now, the current timestamp (2020)

Op za 2 jan. 2021 18:33 schreef Dominik Dorfmeister <
notifications@github.com>:

> *@TkDodo* commented on this pull request.
> ------------------------------
>
> In src/core/query.ts
> <https://github.com/tannerlinsley/react-query/pull/1556#discussion_r550902853>
> :
>
> > @@ -475,7 +475,11 @@ export class Query<
>      return {
>        data,
>        dataUpdateCount: 0,
> -      dataUpdatedAt: hasData ? initialDataUpdatedAt || Date.now() : 0,
>
> that's true, but what else should it be? The types clearly state that it
> should be number | undefined, and the docs say that it has to be *a JS
> timestamp in milliseconds*.
>
> I don't think that we need to check every option for every possible input
> - that's what TypeScript is for. Yes, there are also (lots of) JS
> consumers, but if I put in a string ("1608412420052") - would it not be
> weird if that were converted to Date.now() ?
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/tannerlinsley/react-query/pull/1556#discussion_r550902853>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AIOEZFJORWV5BUG4OH3NT5TSX5KEXANCNFSM4VRFT4GQ>
> .
>

TkDodo: > The problem is that a valid timestamp, 0 (number, 1970) is falsy, and
therefore gets converted to date now, the current timestamp (2020)

Yes, I think I got the problem, and using `??` instead of `||` would solve it with minimal changes. This is one of the prime reasons why nullish coalesce was introduced - so that you can easily get "fallback values" without trapping into the js falsy-ness of the number 0 / empty string. It will only convert `undefined` or `null` to `new Date()` and nothing else, which I think was the intention of the original code.
meesvandongen: Ah sorry, I misunderstood nullish coalescing, and therefore your comment. I see now, it makes a lot of sense. I will change the PR accordingly.
meesvandongen: Alright, I updated the PR, was thinking whether I should have `0` or `Date.now()` on line 471, but ultimately this value would never be used anyway in the current implementation and is only used to satisfy typescript.
boschni: Nice! Could you add a test to cover this bug?

Patch:
diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js
index 37bab164b..9284986cf 100644
--- a/docs/src/pages/index.js
+++ b/docs/src/pages/index.js
@@ -47,6 +47,7 @@ const fans = [
   'Sara Bee, (@doeg)',
   'Mihir Kedia (@meitros)',
   'Nick Quebbeman (@kidqueb)',
+  'Greg Lin (@glin)',
 ]
 
 const Home = () => {
diff --git a/src/core/query.ts b/src/core/query.ts
index f832748d9..0dd52ed47 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -464,18 +464,18 @@ export class Query<
 
     const hasInitialData = typeof options.initialData !== 'undefined'
 
-    const initialDataUpdatedAt =
-      hasInitialData &&
-      (typeof options.initialDataUpdatedAt === 'function'
+    const initialDataUpdatedAt = hasInitialData
+      ? typeof options.initialDataUpdatedAt === 'function'
         ? (options.initialDataUpdatedAt as () => number | undefined)()
-        : options.initialDataUpdatedAt)
+        : options.initialDataUpdatedAt
+      : 0
 
     const hasData = typeof data !== 'undefined'
 
     return {
       data,
       dataUpdateCount: 0,
-      dataUpdatedAt: hasData ? initialDataUpdatedAt || Date.now() : 0,
+      dataUpdatedAt: hasData ? initialDataUpdatedAt ?? Date.now() : 0,
       error: null,
       errorUpdateCount: 0,
       errorUpdatedAt: 0,
diff --git a/src/devtools/devtools.tsx b/src/devtools/devtools.tsx
index d868eac49..6b7a6867c 100644
--- a/src/devtools/devtools.tsx
+++ b/src/devtools/devtools.tsx
@@ -14,7 +14,6 @@ import {
   Code,
   Input,
   Select,
-  QueryCountStyles,
   ActiveQueryPanel,
 } from './styledComponents'
 import { ThemeProvider } from './theme'
diff --git a/src/devtools/styledComponents.ts b/src/devtools/styledComponents.ts
index 63c1e8eb9..1b55ee1cd 100644
--- a/src/devtools/styledComponents.ts
+++ b/src/devtools/styledComponents.ts
@@ -106,17 +106,3 @@ export const Select = styled(
     },
   }
 )
-
-export const QueryCountStyles = styled(
-  'div',
-  {
-    fontSize: '1.2rem',
-    display: 'flex',
-    flexDirection: 'column',
-  },
-  {
-    '(max-width: 500px)': {
-      display: 'none',
-    },
-  }
-)


Test patch:
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index abed72a63..cd879164c 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -2107,6 +2107,37 @@ describe('useQuery', () => {
     })
   })
 
+  it('should fetch if "initial data updated at" is exactly 0', async () => {
+    const key = queryKey()
+    const states: UseQueryResult<string>[] = []
+
+    function Page() {
+      const state = useQuery(key, () => 'data', {
+        staleTime: 10 * 1000, // 10 seconds
+        initialData: 'initial',
+        initialDataUpdatedAt: 0,
+      })
+      states.push(state)
+      return null
+    }
+
+    renderWithClient(queryClient, <Page />)
+
+    await sleep(100)
+
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({
+      data: 'initial',
+      isStale: true,
+      isFetching: true,
+    })
+    expect(states[1]).toMatchObject({
+      data: 'data',
+      isStale: false,
+      isFetching: false,
+    })
+  })
+
   it('should keep initial data when the query key changes', async () => {
     const key = queryKey()
     const states: UseQueryResult<{ count: number }>[] = []