From 10aab8f165ca898752888117bcf5e7adc6ff05df Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 5 Nov 2025 10:09:44 -0600 Subject: [PATCH 1/6] fix(react): Set isLoaded to false during transitive state --- .../src/hooks/__tests__/useAuth.test.tsx | 26 +++++++++++++++++++ packages/react/src/hooks/useAuth.ts | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/react/src/hooks/__tests__/useAuth.test.tsx b/packages/react/src/hooks/__tests__/useAuth.test.tsx index add6740921c..ce430413fd8 100644 --- a/packages/react/src/hooks/__tests__/useAuth.test.tsx +++ b/packages/react/src/hooks/__tests__/useAuth.test.tsx @@ -317,4 +317,30 @@ describe('useDerivedAuth', () => { current.has?.({ permission: 'org:sys_foo' }); }); + + it('returns not loaded state during transitive state (undefined values after being loaded)', () => { + const authObject = { + sessionId: undefined, + userId: undefined, + sessionStatus: undefined, + sessionClaims: null, + actor: undefined, + orgId: undefined, + orgRole: undefined, + orgSlug: undefined, + orgPermissions: undefined, + factorVerificationAge: null, + signOut: vi.fn(), + getToken: vi.fn(), + }; + + const { + result: { current }, + } = renderHook(() => useDerivedAuth(authObject)); + + expect(current.isLoaded).toBe(false); + expect(current.isSignedIn).toBeUndefined(); + expect(current.sessionId).toBeUndefined(); + expect(current.userId).toBeUndefined(); + }); }); diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts index 6a901d69552..36a76aa64ca 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -99,13 +99,13 @@ export const useAuth = (initialAuthStateOrOptions: UseAuthOptions = {}): UseAuth const initialAuthState = rest as any; const authContextFromHook = useAuthContext(); + const isomorphicClerk = useIsomorphicClerkContext(); let authContext = authContextFromHook; - if (authContext.sessionId === undefined && authContext.userId === undefined) { + if (!isomorphicClerk.loaded && authContext.sessionId === undefined && authContext.userId === undefined) { authContext = initialAuthState != null ? initialAuthState : {}; } - const isomorphicClerk = useIsomorphicClerkContext(); const getToken: GetToken = useCallback(createGetToken(isomorphicClerk), [isomorphicClerk]); const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]); From 67581b5c0c6f8f145e1992e60327a5fa82686f1a Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 5 Nov 2025 10:25:04 -0600 Subject: [PATCH 2/6] wip --- packages/react/src/hooks/__tests__/useAuth.test.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react/src/hooks/__tests__/useAuth.test.tsx b/packages/react/src/hooks/__tests__/useAuth.test.tsx index ce430413fd8..a5fa6c192a5 100644 --- a/packages/react/src/hooks/__tests__/useAuth.test.tsx +++ b/packages/react/src/hooks/__tests__/useAuth.test.tsx @@ -320,16 +320,16 @@ describe('useDerivedAuth', () => { it('returns not loaded state during transitive state (undefined values after being loaded)', () => { const authObject = { - sessionId: undefined, - userId: undefined, - sessionStatus: undefined, - sessionClaims: null, actor: undefined, + factorVerificationAge: null, orgId: undefined, + orgPermissions: undefined, orgRole: undefined, orgSlug: undefined, - orgPermissions: undefined, - factorVerificationAge: null, + sessionClaims: null, + sessionId: undefined, + sessionStatus: undefined, + userId: undefined, signOut: vi.fn(), getToken: vi.fn(), }; From 35b286593200b173e6024c960f3030c920163b75 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 5 Nov 2025 10:25:20 -0600 Subject: [PATCH 3/6] wip --- packages/react/src/hooks/__tests__/useAuth.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/hooks/__tests__/useAuth.test.tsx b/packages/react/src/hooks/__tests__/useAuth.test.tsx index a5fa6c192a5..4ecdb4f01d7 100644 --- a/packages/react/src/hooks/__tests__/useAuth.test.tsx +++ b/packages/react/src/hooks/__tests__/useAuth.test.tsx @@ -318,7 +318,7 @@ describe('useDerivedAuth', () => { current.has?.({ permission: 'org:sys_foo' }); }); - it('returns not loaded state during transitive state (undefined values after being loaded)', () => { + it('returns not loaded state during transitive state', () => { const authObject = { actor: undefined, factorVerificationAge: null, From eb81b286e3906272c997ea4ea31fcf666f5019ef Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 5 Nov 2025 14:17:15 -0600 Subject: [PATCH 4/6] changeset --- .changeset/full-showers-serve.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/full-showers-serve.md diff --git a/.changeset/full-showers-serve.md b/.changeset/full-showers-serve.md new file mode 100644 index 00000000000..242b674902f --- /dev/null +++ b/.changeset/full-showers-serve.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-react': patch +--- + +Ensure that useAuth() hook returns isLoaded=false when isomorphicClerk is loaded but we are in transitive state From 77d7628b3c657ff063e00b7e4b94d7f8115e6ae5 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 5 Nov 2025 14:35:23 -0600 Subject: [PATCH 5/6] Update test --- .../src/hooks/__tests__/useAuth.test.tsx | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/react/src/hooks/__tests__/useAuth.test.tsx b/packages/react/src/hooks/__tests__/useAuth.test.tsx index 4ecdb4f01d7..d5f2dfbc694 100644 --- a/packages/react/src/hooks/__tests__/useAuth.test.tsx +++ b/packages/react/src/hooks/__tests__/useAuth.test.tsx @@ -77,6 +77,39 @@ describe('useAuth', () => { ); }).not.toThrow(); }); + + test('returns isLoaded false when isomorphicClerk is loaded but in transitive state', () => { + const mockIsomorphicClerk = { + loaded: true, + telemetry: { record: vi.fn() }, + }; + + const mockAuthContext = { + actor: undefined, + factorVerificationAge: null, + orgId: undefined, + orgPermissions: undefined, + orgRole: undefined, + orgSlug: undefined, + sessionClaims: null, + sessionId: undefined, + sessionStatus: undefined, + userId: undefined, + }; + + const { result } = renderHook(() => useAuth(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current.isLoaded).toBe(false); + expect(result.current.isSignedIn).toBeUndefined(); + expect(result.current.sessionId).toBeUndefined(); + expect(result.current.userId).toBeUndefined(); + }); }); describe('useDerivedAuth', () => { @@ -317,30 +350,4 @@ describe('useDerivedAuth', () => { current.has?.({ permission: 'org:sys_foo' }); }); - - it('returns not loaded state during transitive state', () => { - const authObject = { - actor: undefined, - factorVerificationAge: null, - orgId: undefined, - orgPermissions: undefined, - orgRole: undefined, - orgSlug: undefined, - sessionClaims: null, - sessionId: undefined, - sessionStatus: undefined, - userId: undefined, - signOut: vi.fn(), - getToken: vi.fn(), - }; - - const { - result: { current }, - } = renderHook(() => useDerivedAuth(authObject)); - - expect(current.isLoaded).toBe(false); - expect(current.isSignedIn).toBeUndefined(); - expect(current.sessionId).toBeUndefined(); - expect(current.userId).toBeUndefined(); - }); }); From 044d9ca23d08086351d292f2d4fe79ca35e6a8aa Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 5 Nov 2025 20:46:32 -0600 Subject: [PATCH 6/6] simplify logic --- packages/react/src/hooks/useAuth.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts index 36a76aa64ca..486e549d872 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -100,11 +100,7 @@ export const useAuth = (initialAuthStateOrOptions: UseAuthOptions = {}): UseAuth const authContextFromHook = useAuthContext(); const isomorphicClerk = useIsomorphicClerkContext(); - let authContext = authContextFromHook; - - if (!isomorphicClerk.loaded && authContext.sessionId === undefined && authContext.userId === undefined) { - authContext = initialAuthState != null ? initialAuthState : {}; - } + const authContext = !isomorphicClerk.loaded && initialAuthState ? initialAuthState : authContextFromHook; const getToken: GetToken = useCallback(createGetToken(isomorphicClerk), [isomorphicClerk]); const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]);