From 1387cd7651882015ea2636d4605e7c067b4e3194 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 23 Apr 2026 11:38:39 -0500 Subject: [PATCH 1/2] fix(shared): Correct isLoading invariant in useBaseQuery pre-client window Report isLoading: true when the React Query client has not yet attached and the query is enabled. This restores the documented !isLoaded || resource.isLoading loading gate for paginated resources in useOrganizationList and useOrganization, which previously reported isLoaded: true with isLoading: false and data: [] during the render between clerk-js loading and the query client being ready. --- .../fix-useorganizationlist-empty-render.md | 5 + .../clerk-rq/__tests__/useBaseQuery.spec.tsx | 130 ++++++++++++++++++ .../shared/src/react/clerk-rq/useBaseQuery.ts | 11 +- 3 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-useorganizationlist-empty-render.md create mode 100644 packages/shared/src/react/clerk-rq/__tests__/useBaseQuery.spec.tsx diff --git a/.changeset/fix-useorganizationlist-empty-render.md b/.changeset/fix-useorganizationlist-empty-render.md new file mode 100644 index 00000000000..1a90fdf5ee1 --- /dev/null +++ b/.changeset/fix-useorganizationlist-empty-render.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': patch +--- + +Fix `useOrganizationList` and `useOrganization` returning `isLoaded: true` with `userMemberships.isLoading: false` and `data: []` during the brief window between clerk-js loading and the React Query client attaching. `useBaseQuery` now reports `isLoading: true` while the query client is not yet loaded, as long as the query is enabled — restoring the documented `!isLoaded || resource.isLoading` loading gate for paginated resources. diff --git a/packages/shared/src/react/clerk-rq/__tests__/useBaseQuery.spec.tsx b/packages/shared/src/react/clerk-rq/__tests__/useBaseQuery.spec.tsx new file mode 100644 index 00000000000..0172ab99f30 --- /dev/null +++ b/packages/shared/src/react/clerk-rq/__tests__/useBaseQuery.spec.tsx @@ -0,0 +1,130 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import React from 'react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createMockClerk, createMockQueryClient } from '../../hooks/__tests__/mocks/clerk'; +import { useClerkInfiniteQuery } from '../useInfiniteQuery'; +import { useClerkQuery } from '../useQuery'; + +let activeClerk: any; + +vi.mock('../../contexts', () => ({ + useAssertWrappedByClerkProvider: () => {}, + useClerkInstanceContext: () => activeClerk, + useInitialStateContext: () => undefined, +})); + +const wrapper = ({ children }: { children: React.ReactNode }) => <>{children}; + +const makeClerkWithoutQueryClient = () => { + const mockClerk = createMockClerk({ queryClient: null }); + Object.defineProperty(mockClerk, '__internal_queryClient', { + get: () => undefined, + configurable: true, + }); + return mockClerk; +}; + +afterEach(() => { + vi.clearAllMocks(); +}); + +describe('useBaseQuery - dummy result while query client is not attached', () => { + beforeEach(() => { + activeClerk = makeClerkWithoutQueryClient(); + }); + + it('reports isLoading: true when the query would be enabled', () => { + const queryFn = vi.fn(); + const { result } = renderHook( + () => + useClerkQuery({ + queryKey: ['useBaseQuery-pre-client-enabled'], + queryFn, + enabled: true, + }), + { wrapper }, + ); + + expect(result.current.isLoading).toBe(true); + expect(result.current.isFetching).toBe(false); + expect(result.current.status).toBe('pending'); + expect(result.current.data).toBeUndefined(); + expect(queryFn).not.toHaveBeenCalled(); + }); + + it('reports isLoading: false when enabled is explicitly false', () => { + const queryFn = vi.fn(); + const { result } = renderHook( + () => + useClerkQuery({ + queryKey: ['useBaseQuery-pre-client-disabled'], + queryFn, + enabled: false, + }), + { wrapper }, + ); + + expect(result.current.isLoading).toBe(false); + expect(result.current.isFetching).toBe(false); + expect(result.current.status).toBe('pending'); + expect(result.current.data).toBeUndefined(); + expect(queryFn).not.toHaveBeenCalled(); + }); + + it('defaults to enabled when the option is omitted', () => { + const queryFn = vi.fn(); + const { result } = renderHook( + () => + useClerkQuery({ + queryKey: ['useBaseQuery-pre-client-default'], + queryFn, + }), + { wrapper }, + ); + + expect(result.current.isLoading).toBe(true); + }); + + it('applies the same invariant to useClerkInfiniteQuery', () => { + const queryFn = vi.fn(); + const { result } = renderHook( + () => + useClerkInfiniteQuery({ + queryKey: ['useBaseQuery-pre-client-infinite'], + queryFn, + initialPageParam: 1, + getNextPageParam: () => undefined, + enabled: true, + }), + { wrapper }, + ); + + expect(result.current.isLoading).toBe(true); + expect(result.current.isFetching).toBe(false); + expect(result.current.data).toBeUndefined(); + expect(queryFn).not.toHaveBeenCalled(); + }); +}); + +describe('useBaseQuery - normal behavior once query client attaches', () => { + it('delegates to the real observer when the query client is loaded', async () => { + const queryClient = createMockQueryClient(); + activeClerk = createMockClerk({ queryClient }); + + const queryFn = vi.fn(async () => 'result'); + const { result } = renderHook( + () => + useClerkQuery({ + queryKey: ['useBaseQuery-loaded-client'], + queryFn, + }), + { wrapper }, + ); + + expect(result.current.isLoading).toBe(true); + await waitFor(() => expect(result.current.isLoading).toBe(false)); + expect(result.current.data).toBe('result'); + expect(queryFn).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/shared/src/react/clerk-rq/useBaseQuery.ts b/packages/shared/src/react/clerk-rq/useBaseQuery.ts index 84ef1558a24..2c044590b1d 100644 --- a/packages/shared/src/react/clerk-rq/useBaseQuery.ts +++ b/packages/shared/src/react/clerk-rq/useBaseQuery.ts @@ -62,13 +62,16 @@ export function useBaseQuery Date: Thu, 23 Apr 2026 11:47:58 -0500 Subject: [PATCH 2/2] chore(shared): Shorten changeset --- .changeset/fix-useorganizationlist-empty-render.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fix-useorganizationlist-empty-render.md b/.changeset/fix-useorganizationlist-empty-render.md index 1a90fdf5ee1..2c5b04fae31 100644 --- a/.changeset/fix-useorganizationlist-empty-render.md +++ b/.changeset/fix-useorganizationlist-empty-render.md @@ -2,4 +2,4 @@ '@clerk/shared': patch --- -Fix `useOrganizationList` and `useOrganization` returning `isLoaded: true` with `userMemberships.isLoading: false` and `data: []` during the brief window between clerk-js loading and the React Query client attaching. `useBaseQuery` now reports `isLoading: true` while the query client is not yet loaded, as long as the query is enabled — restoring the documented `!isLoaded || resource.isLoading` loading gate for paginated resources. +Fix `useOrganizationList` and `useOrganization` briefly reporting paginated resources as `isLoading: false` with empty data before the query starts.