Repository: tannerlinsley/react-query
Pull request #2130 (2021-04-15 10:22:29)

feat(query): add 'getObserversCount' util
Adding 'getObserversCount' util

Reson for making this change:

The [observers](https://github.com/tannerlinsley/react-query/blob/master/src/core/query.ts#L159) on the query is private property, therefore Typescript is complaining when trying to use it.

DevTools is using the length of the observers in a couple of places like [here](https://github.com/tannerlinsley/react-query/blob/master/src/devtools/utils.ts#L23)

Since this property should probably stay as private, I have added utility to get the observers count as a getter method.

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


codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/tannerlinsley/react-query/pr/2130/builds/122628) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 6eaf163f9c07f8a3e67ab6fb4275e7d0868eba78:

|Sandbox| Source |
|--|--|
|[tannerlinsley/react-query: basic](https://codesandbox.io/s/tannerlinsleyreact-query-basic-ribm2)| Configuration |
|[tannerlinsley/react-query: basic-typescript](https://codesandbox.io/s/tannerlinsleyreact-query-basic-typescript-jwjvi)| Configuration |

DamianOsipiuk: @TkDodo Hi, any chance this could be merged? :wink: 
TkDodo: > DevTools is using the length of the observers in a couple of places like here

sure, can you maybe use new function then in the devtools and replace the private accesses with it?
DamianOsipiuk: @TkDodo Sure :+1:
TkDodo: it's also used in tests with ts-expects-error as far as I can see :)

Patch:
diff --git a/src/core/query.ts b/src/core/query.ts
index 4786aa6d6..10394221a 100644
--- a/src/core/query.ts
+++ b/src/core/query.ts
@@ -342,6 +342,10 @@ export class Query<
     }
   }
 
+  getObserversCount(): number {
+    return this.observers.length
+  }
+
   invalidate(): void {
     if (!this.state.isInvalidated) {
       this.dispatch({ type: 'invalidate' })
diff --git a/src/devtools/devtools.tsx b/src/devtools/devtools.tsx
index 346bc247f..449567992 100644
--- a/src/devtools/devtools.tsx
+++ b/src/devtools/devtools.tsx
@@ -306,7 +306,7 @@ export function ReactQueryDevtools({
 }
 
 const getStatusRank = q =>
-  q.state.isFetching ? 0 : !q.observers.length ? 3 : q.isStale() ? 2 : 1
+  q.state.isFetching ? 0 : !q.getObserversCount() ? 3 : q.isStale() ? 2 : 1
 
 const sortFns = {
   'Status > Last Updated': (a, b) =>
@@ -613,7 +613,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef(
                           : 'white',
                     }}
                   >
-                    {query.observers.length}
+                    {query.getObserversCount()}
                   </div>
                   <Code
                     suppressHydrationWarning
@@ -689,7 +689,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef(
                     justifyContent: 'space-between',
                   }}
                 >
-                  Observers: <Code>{activeQuery.observers.length}</Code>
+                  Observers: <Code>{activeQuery.getObserversCount()}</Code>
                 </div>
                 <div
                   style={{
diff --git a/src/devtools/utils.ts b/src/devtools/utils.ts
index 9a5d94ba5..a5c7b7829 100644
--- a/src/devtools/utils.ts
+++ b/src/devtools/utils.ts
@@ -12,7 +12,7 @@ export function getQueryStatusColor(query, theme) {
     ? theme.active
     : query.isStale()
     ? theme.warning
-    : !query.observers.length
+    : !query.getObserversCount()
     ? theme.gray
     : theme.success
 }
@@ -20,7 +20,7 @@ export function getQueryStatusColor(query, theme) {
 export function getQueryStatusLabel(query) {
   return query.state.isFetching
     ? 'fetching'
-    : !query.observers.length
+    : !query.getObserversCount()
     ? 'inactive'
     : query.isStale()
     ? 'stale'


Test patch:
diff --git a/src/core/tests/query.test.tsx b/src/core/tests/query.test.tsx
index 6b118925a..a9883c205 100644
--- a/src/core/tests/query.test.tsx
+++ b/src/core/tests/query.test.tsx
@@ -447,4 +447,29 @@ describe('query', () => {
     await sleep(100)
     expect(queryCache.find(key)).toBeDefined()
   })
+
+  test('should return proper count of observers', async () => {
+    const key = queryKey()
+    const options = { queryKey: key, queryFn: async () => 'data' }
+    const observer = new QueryObserver(queryClient, options)
+    const observer2 = new QueryObserver(queryClient, options)
+    const observer3 = new QueryObserver(queryClient, options)
+    const query = queryCache.find(key)
+
+    expect(query?.getObserversCount()).toEqual(0)
+
+    const unsubscribe1 = observer.subscribe()
+    const unsubscribe2 = observer2.subscribe()
+    const unsubscribe3 = observer3.subscribe()
+    expect(query?.getObserversCount()).toEqual(3)
+
+    unsubscribe3()
+    expect(query?.getObserversCount()).toEqual(2)
+
+    unsubscribe2()
+    expect(query?.getObserversCount()).toEqual(1)
+
+    unsubscribe1()
+    expect(query?.getObserversCount()).toEqual(0)
+  })
 })
diff --git a/src/react/tests/suspense.test.tsx b/src/react/tests/suspense.test.tsx
index 30450332d..13077a4c3 100644
--- a/src/react/tests/suspense.test.tsx
+++ b/src/react/tests/suspense.test.tsx
@@ -162,14 +162,12 @@ describe("useQuery's in Suspense mode", () => {
     fireEvent.click(rendered.getByLabelText('toggle'))
     await waitFor(() => rendered.getByText('rendered'))
 
-    // @ts-expect-error
-    expect(queryCache.find(key)?.observers.length).toBe(1)
+    expect(queryCache.find(key)?.getObserversCount()).toBe(1)
 
     fireEvent.click(rendered.getByLabelText('toggle'))
 
     expect(rendered.queryByText('rendered')).toBeNull()
-    // @ts-expect-error
-    expect(queryCache.find(key)?.observers.length).toBe(0)
+    expect(queryCache.find(key)?.getObserversCount()).toBe(0)
   })
 
   it('should call onSuccess on the first successful call', async () => {