From fbb4998625cbc82d29fea706dfe8651b82e93318 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sat, 22 Oct 2022 13:44:25 +0900 Subject: [PATCH 1/3] fix(clerk-react): Added useAuth test and made auth, getToken, and signOut stable --- .../src/contexts/ClerkContextProvider.tsx | 4 +- packages/react/src/hooks/useAuth.test.tsx | 49 ++++++++ packages/react/src/hooks/useAuth.ts | 110 +++++++++--------- 3 files changed, 103 insertions(+), 60 deletions(-) create mode 100644 packages/react/src/hooks/useAuth.test.tsx 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..28ae6a1b3af --- /dev/null +++ b/packages/react/src/hooks/useAuth.test.tsx @@ -0,0 +1,49 @@ +import { render, screen, userEvent } from '@clerk/shared/testUtils'; +import React, { useEffect, useState } from 'react'; + +import { useAuth } from './useAuth'; +import { ClerkProvider } from '../contexts/ClerkProvider'; + +const withFakeClerk = Component => { + return props => ( + + + + ); +}; + +describe('useAuth()', () => { + it('auth is stable between re-renders', async () => { + let renderCount = 0; + const MyDemo = () => { + const [count, setCount] = useState(0); + const auth = useAuth(); + useEffect(() => { + renderCount++; + }, [auth]); + return ; + }; + const App = withFakeClerk(MyDemo); + render(); + const btn = screen.getByText('Hi'); + userEvent.click(btn); + expect(renderCount).toBe(1); + }); + + it('getToken is stable between re-renders', async () => { + let renderCount = 0; + const MyDemo = () => { + const [count, setCount] = useState(0); + const { getToken } = useAuth(); + useEffect(() => { + renderCount++; + }, [getToken]); + return ; + }; + const App = withFakeClerk(MyDemo); + render(); + const btn = screen.getByText('Hi'); + userEvent.click(btn); + expect(renderCount).toBe(1); + }); +}); diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts index ae5e8e1f1a6..c50d7ca4272 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -1,38 +1,20 @@ import type { ActJWTClaim, GetToken, SignOut } from '@clerk/types'; +import { useMemo } from 'react'; import { useAuthContext } from '../contexts/AuthContext'; import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext'; import { invalidStateError } from '../errors'; 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 UseAuthReturn = { + isLoaded: boolean; + isSignedIn: boolean | undefined; + userId: string | null | undefined; + sessionId: string | null | undefined; + actor: ActJWTClaim | null; + signOut: SignOut; + getToken: GetToken; +}; type UseAuth = () => UseAuthReturn; @@ -79,44 +61,56 @@ 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 = useMemo(() => { + return createGetToken(isomorphicClerk); + }, [isomorphicClerk]); + const signOut: SignOut = useMemo(() => { + return createSignOut(isomorphicClerk); + }, [isomorphicClerk]); + + // Make this stable as well! + const auth: UseAuthReturn = { + // These come from somewhere else; considered stable here + sessionId, + userId, + signOut, + getToken, + + // These 3 vary depending on the conditions + isLoaded: false, + isSignedIn: false, + actor: null, + }; + let isValidState = false; if (sessionId === undefined && userId === undefined) { - return { - isLoaded: false, - isSignedIn: undefined, - sessionId, - userId, - actor: null, - signOut, - getToken, - }; + isValidState = true; + auth.isLoaded = false; + auth.isSignedIn = undefined; + auth.actor = null; } if (sessionId === null && userId === null) { - return { - isLoaded: true, - isSignedIn: false, - sessionId, - userId, - actor: null, - signOut, - getToken, - }; + isValidState = true; + auth.isLoaded = true; + auth.isSignedIn = undefined; + auth.actor = actor; } if (!!sessionId && !!userId) { - return { - isLoaded: true, - isSignedIn: true, - sessionId, - userId, - actor, - signOut, - getToken, - }; + isValidState = true; + auth.isLoaded = true; + auth.isSignedIn = true; + auth.actor = actor; + } + + // When any of the values of auth changes, create a new auth; but if + // none of them change, keep auth also stable! + const StableAuth = useMemo(() => auth, Object.values(auth)); + + if (!isValidState) { + throw new Error(invalidStateError); } - throw new Error(invalidStateError); + return StableAuth; }; From 65004ec0c0a6c9d2bbe32ffbd4b32bf5133934f6 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sun, 30 Oct 2022 00:45:09 +0900 Subject: [PATCH 2/3] fix(clerk-react): Added useAuth test and made auth, getToken, and signOut stable --- packages/react/src/hooks/useAuth.test.tsx | 71 ++++++++------ packages/react/src/hooks/useAuth.ts | 109 ++++++++++++---------- 2 files changed, 101 insertions(+), 79 deletions(-) diff --git a/packages/react/src/hooks/useAuth.test.tsx b/packages/react/src/hooks/useAuth.test.tsx index 28ae6a1b3af..fb37212933f 100644 --- a/packages/react/src/hooks/useAuth.test.tsx +++ b/packages/react/src/hooks/useAuth.test.tsx @@ -1,49 +1,62 @@ -import { render, screen, userEvent } from '@clerk/shared/testUtils'; -import React, { useEffect, useState } from 'react'; +import { render } from '@clerk/shared/testUtils'; +import React, { useEffect } from 'react'; import { useAuth } from './useAuth'; import { ClerkProvider } from '../contexts/ClerkProvider'; -const withFakeClerk = Component => { - return props => ( - - - - ); -}; +import { withClerk } from '../components/withClerk'; + +jest.mock('../components/withClerk', () => ({ + withClerk: (Component: any) => (props: any) => { + return ( + + ); + }, +})); describe('useAuth()', () => { - it('auth is stable between re-renders', async () => { + it('getToken is stable between re-renders', async () => { let renderCount = 0; - const MyDemo = () => { - const [count, setCount] = useState(0); - const auth = useAuth(); + const MyDemo = ({ id }: { id: number }) => { + const { getToken } = useAuth(); useEffect(() => { renderCount++; - }, [auth]); - return ; + }, [getToken]); + return ; }; - const App = withFakeClerk(MyDemo); - render(); - const btn = screen.getByText('Hi'); - userEvent.click(btn); + const App = withClerk(({ id }: { id: number; clerk }) => ( + + + + )); + expect(renderCount).toBe(0); + const { rerender } = render(); + expect(renderCount).toBe(1); + rerender(); expect(renderCount).toBe(1); }); - it('getToken is stable between re-renders', async () => { + it('signOut is stable between re-renders', async () => { let renderCount = 0; - const MyDemo = () => { - const [count, setCount] = useState(0); - const { getToken } = useAuth(); + const MyDemo = ({ id }: { id: number }) => { + const { signOut } = useAuth(); useEffect(() => { renderCount++; - }, [getToken]); - return ; + }, [signOut]); + return ; }; - const App = withFakeClerk(MyDemo); - render(); - const btn = screen.getByText('Hi'); - userEvent.click(btn); + 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 c50d7ca4272..99bb2fca34b 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -1,22 +1,43 @@ import type { ActJWTClaim, GetToken, SignOut } from '@clerk/types'; -import { useMemo } from 'react'; +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: boolean; - isSignedIn: boolean | undefined; - userId: string | null | undefined; - sessionId: string | null | undefined; +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` @@ -59,58 +80,46 @@ type UseAuth = () => UseAuthReturn; */ export const useAuth: UseAuth = () => { const { sessionId, userId, actor } = useAuthContext(); - const isomorphicClerk = useIsomorphicClerkContext(); - - const getToken: GetToken = useMemo(() => { - return createGetToken(isomorphicClerk); - }, [isomorphicClerk]); - const signOut: SignOut = useMemo(() => { - return createSignOut(isomorphicClerk); - }, [isomorphicClerk]); - - // Make this stable as well! - const auth: UseAuthReturn = { - // These come from somewhere else; considered stable here - sessionId, - userId, - signOut, - getToken, + const isomorphicClerk = useIsomorphicClerkContext() as unknown as IsomorphicClerk; - // These 3 vary depending on the conditions - isLoaded: false, - isSignedIn: false, - actor: null, - }; + const getToken: GetToken = useCallback(createGetToken(isomorphicClerk), [isomorphicClerk]); + const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]); - let isValidState = false; if (sessionId === undefined && userId === undefined) { - isValidState = true; - auth.isLoaded = false; - auth.isSignedIn = undefined; - auth.actor = null; + return { + isLoaded: false, + isSignedIn: undefined, + sessionId, + userId, + actor: null, + signOut, + getToken, + } as InitReturn; } if (sessionId === null && userId === null) { - isValidState = true; - auth.isLoaded = true; - auth.isSignedIn = undefined; - auth.actor = actor; + return { + isLoaded: true, + isSignedIn: false, + sessionId, + userId, + actor: null, + signOut, + getToken, + } as LoadedReturn; } if (!!sessionId && !!userId) { - isValidState = true; - auth.isLoaded = true; - auth.isSignedIn = true; - auth.actor = actor; - } - - // When any of the values of auth changes, create a new auth; but if - // none of them change, keep auth also stable! - const StableAuth = useMemo(() => auth, Object.values(auth)); - - if (!isValidState) { - throw new Error(invalidStateError); + return { + isLoaded: true, + isSignedIn: true, + sessionId, + userId, + actor, + signOut, + getToken, + } as UserReturn; } - return StableAuth; + throw new Error(invalidStateError); }; From 7d658481bdeea149d02ec8182bce9fb057c917d9 Mon Sep 17 00:00:00 2001 From: Francisco Presencia Date: Sun, 30 Oct 2022 01:04:31 +0900 Subject: [PATCH 3/3] Update packages/react/src/hooks/useAuth.ts Not sure what that was about --- packages/react/src/hooks/useAuth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts index 99bb2fca34b..1a3976a5298 100644 --- a/packages/react/src/hooks/useAuth.ts +++ b/packages/react/src/hooks/useAuth.ts @@ -80,7 +80,7 @@ type UseAuth = () => InitReturn | LoadedReturn | UserReturn; */ export const useAuth: UseAuth = () => { const { sessionId, userId, actor } = useAuthContext(); - const isomorphicClerk = useIsomorphicClerkContext() as unknown as IsomorphicClerk; + const isomorphicClerk = useIsomorphicClerkContext(); const getToken: GetToken = useCallback(createGetToken(isomorphicClerk), [isomorphicClerk]); const signOut: SignOut = useCallback(createSignOut(isomorphicClerk), [isomorphicClerk]);