diff --git a/packages/react/src/contexts/ClerkContextProvider.tsx b/packages/react/src/contexts/ClerkContextProvider.tsx
index e974b21cf96..3133ca3f8de 100644
--- a/packages/react/src/contexts/ClerkContextProvider.tsx
+++ b/packages/react/src/contexts/ClerkContextProvider.tsx
@@ -146,9 +146,9 @@ function deriveState(
lastOrganizationMember: null,
};
}
- const userId: string | null | undefined = state.user ? state.user.id : state.user;
+ const userId: string | null | undefined = state.user ? state.user.id : (state.user as null | undefined);
const user = state.user;
- const sessionId: string | null | undefined = state.session ? state.session.id : state.session;
+ const sessionId: string | null | undefined = state.session ? state.session.id : (state.session as null | undefined);
const session = state.session;
const organization = state.organization;
const lastOrganizationInvitation = state.lastOrganizationInvitation;
diff --git a/packages/react/src/hooks/useAuth.test.tsx b/packages/react/src/hooks/useAuth.test.tsx
new file mode 100644
index 00000000000..fb37212933f
--- /dev/null
+++ b/packages/react/src/hooks/useAuth.test.tsx
@@ -0,0 +1,62 @@
+import { render } from '@clerk/shared/testUtils';
+import React, { useEffect } from 'react';
+
+import { useAuth } from './useAuth';
+import { ClerkProvider } from '../contexts/ClerkProvider';
+
+import { withClerk } from '../components/withClerk';
+
+jest.mock('../components/withClerk', () => ({
+ withClerk: (Component: any) => (props: any) => {
+ return (
+
+ );
+ },
+}));
+
+describe('useAuth()', () => {
+ it('getToken is stable between re-renders', async () => {
+ let renderCount = 0;
+ const MyDemo = ({ id }: { id: number }) => {
+ const { getToken } = useAuth();
+ useEffect(() => {
+ renderCount++;
+ }, [getToken]);
+ return ;
+ };
+ const App = withClerk(({ id }: { id: number; clerk }) => (
+
+
+
+ ));
+ expect(renderCount).toBe(0);
+ const { rerender } = render();
+ expect(renderCount).toBe(1);
+ rerender();
+ expect(renderCount).toBe(1);
+ });
+
+ it('signOut is stable between re-renders', async () => {
+ let renderCount = 0;
+ const MyDemo = ({ id }: { id: number }) => {
+ const { signOut } = useAuth();
+ useEffect(() => {
+ renderCount++;
+ }, [signOut]);
+ return ;
+ };
+ const App = withClerk(({ id }: { id: number; clerk }) => (
+
+
+
+ ));
+ expect(renderCount).toBe(0);
+ const { rerender } = render();
+ expect(renderCount).toBe(1);
+ rerender();
+ expect(renderCount).toBe(1);
+ });
+});
diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts
index ae5e8e1f1a6..1a3976a5298 100644
--- a/packages/react/src/hooks/useAuth.ts
+++ b/packages/react/src/hooks/useAuth.ts
@@ -1,40 +1,43 @@
import type { ActJWTClaim, GetToken, SignOut } from '@clerk/types';
+import { useCallback, useMemo } from 'react';
import { useAuthContext } from '../contexts/AuthContext';
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { invalidStateError } from '../errors';
+import IsomorphicClerk from '../isomorphicClerk';
import { createGetToken, createSignOut } from './utils';
-type UseAuthReturn =
- | {
- isLoaded: false;
- isSignedIn: undefined;
- userId: undefined;
- sessionId: undefined;
- actor: null;
- signOut: SignOut;
- getToken: GetToken;
- }
- | {
- isLoaded: true;
- isSignedIn: false;
- userId: null;
- sessionId: null;
- actor: null;
- signOut: SignOut;
- getToken: GetToken;
- }
- | {
- isLoaded: true;
- isSignedIn: true;
- userId: string;
- sessionId: string;
- actor: ActJWTClaim | null;
- signOut: SignOut;
- getToken: GetToken;
- };
+type InitReturn = {
+ isLoaded: false;
+ isSignedIn: undefined;
+ userId: undefined;
+ sessionId: undefined;
+ actor: null;
+ signOut: SignOut;
+ getToken: GetToken;
+};
+
+type LoadedReturn = {
+ isLoaded: true;
+ isSignedIn: false;
+ userId: null;
+ sessionId: null;
+ actor: null;
+ signOut: SignOut;
+ getToken: GetToken;
+};
+
+type UserReturn = {
+ isLoaded: true;
+ isSignedIn: true;
+ userId: string;
+ sessionId: string;
+ actor: ActJWTClaim | null;
+ signOut: SignOut;
+ getToken: GetToken;
+};
-type UseAuth = () => UseAuthReturn;
+type UseAuth = () => InitReturn | LoadedReturn | UserReturn;
/**
* Returns the current auth state, the user and session ids and the `getToken`
@@ -79,8 +82,8 @@ export const useAuth: UseAuth = () => {
const { sessionId, userId, actor } = useAuthContext();
const isomorphicClerk = useIsomorphicClerkContext();
- const getToken: GetToken = createGetToken(isomorphicClerk);
- const signOut: SignOut = createSignOut(isomorphicClerk);
+ const getToken: GetToken = useCallback(createGetToken(isomorphicClerk), [isomorphicClerk]);
+ const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]);
if (sessionId === undefined && userId === undefined) {
return {
@@ -91,7 +94,7 @@ export const useAuth: UseAuth = () => {
actor: null,
signOut,
getToken,
- };
+ } as InitReturn;
}
if (sessionId === null && userId === null) {
@@ -103,7 +106,7 @@ export const useAuth: UseAuth = () => {
actor: null,
signOut,
getToken,
- };
+ } as LoadedReturn;
}
if (!!sessionId && !!userId) {
@@ -115,7 +118,7 @@ export const useAuth: UseAuth = () => {
actor,
signOut,
getToken,
- };
+ } as UserReturn;
}
throw new Error(invalidStateError);