Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/full-showers-serve.md
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions packages/react/src/hooks/__tests__/useAuth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(), {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is passing even with the existing logic for me.

I think the prerequisite for the bug to happen is that we explicitly pass in initialAuthState (like we do from usePromisifiedAuth, which is the reason this is failing in Next).

For a test that fails with the old code and pass with the new (might want to update the test description too):

Suggested change
const { result } = renderHook(() => useAuth(), {
const mockInitialAuthState = {
sessionId: null,
userId: null,
};
// Test that we don't fall back to the initial state when
// isomorphicClerk is loaded but in transitive state
const { result } = renderHook(() => useAuth(mockInitialAuthState), {

We could also keep this test as is since it covers a new case we didn't have coverage for, and add a new test with the above.

Overall, I think several tests here might benefit from the test approach you are using here btw, useAuth is the main public API so we should be testing that directly instead of useDerivedAuth.

(One could also argue we should move/duplicate this test to usePromisifiedAuth since that's really the broken public API here, but I don't think we should do that right now)

wrapper: ({ children }) => (
<ClerkInstanceContext.Provider value={{ value: mockIsomorphicClerk as any }}>
<AuthContext.Provider value={{ value: mockAuthContext as any }}>{children}</AuthContext.Provider>
</ClerkInstanceContext.Provider>
),
});

expect(result.current.isLoaded).toBe(false);
expect(result.current.isSignedIn).toBeUndefined();
expect(result.current.sessionId).toBeUndefined();
expect(result.current.userId).toBeUndefined();
});
});

describe('useDerivedAuth', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
Loading