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);