Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions packages/react/src/contexts/ClerkContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ Is this type assertion required?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Without this, npm test will give me this issue:

src/contexts/ClerkContextProvider.tsx:149:9 - error TS2322: Type 'string | UserResource' is not assignable to type 'string'.
      Type 'UserResource' is not assignable to type 'string'.

    149   const userId: string | null | undefined = state.user ? state.user.id : state.user;

Alternatively these also work:

const userId: string | null | undefined = state.user && state.user.id;

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;
Expand Down
62 changes: 62 additions & 0 deletions packages/react/src/hooks/useAuth.test.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Component
{...props}
clerk={{}}
/>
);
},
}));

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 <button>Hi {id}</button>;
};
const App = withClerk(({ id }: { id: number; clerk }) => (
<ClerkProvider frontendApi='https://example.com'>
<MyDemo id={id} />
</ClerkProvider>
));
expect(renderCount).toBe(0);
const { rerender } = render(<App id={0} />);
expect(renderCount).toBe(1);
rerender(<App id={1} />);
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 <button>Hi {id}</button>;
};
const App = withClerk(({ id }: { id: number; clerk }) => (
<ClerkProvider frontendApi='https://example.com'>
<MyDemo id={id} />
</ClerkProvider>
));
expect(renderCount).toBe(0);
const { rerender } = render(<App id={0} />);
expect(renderCount).toBe(1);
rerender(<App id={1} />);
expect(renderCount).toBe(1);
});
});
71 changes: 37 additions & 34 deletions packages/react/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -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 = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A better naming for this interface is the UnknownState as undefined means that we can't tell yet if the user is signed in or signed out.

isLoaded: false;
isSignedIn: undefined;
userId: undefined;
sessionId: undefined;
actor: null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔧 This should also be an undefined. It was a mistake to make it null in this scenario in the first place.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's avoid creating these three different states and stick to the original type definition to avoid the extra assertions.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

If I use the original, TS doesn't seem to differentiate them properly and I get this error:

Type '() => { isLoaded: false; isSignedIn: any; sessionId: any; userId: any; actor: any; signOut: SignOut; getToken: GetToken; } | { isLoaded: true; isSignedIn: false; sessionId: string; userId: string; actor: any; signOut: SignOut; getToken: GetToken; } | { ...; }' is not assignable to type 'UseAuth'.
      Type '{ isLoaded: false; isSignedIn: any; sessionId: any; userId: any; actor: any; signOut: SignOut; getToken: GetToken; } | { isLoaded: true; isSignedIn: false; sessionId: string; userId: string; actor: any; signOut: SignOut; getToken: GetToken; } | { ...; }' is not assignable to type 'UseAuthReturn'.
        Type '{ isLoaded: true; isSignedIn: false; sessionId: string; userId: string; actor: any; signOut: SignOut; getToken: GetToken; }' is not assignable to type 'UseAuthReturn'.
          Type '{ isLoaded: true; isSignedIn: false; sessionId: string; userId: string; actor: any; signOut: SignOut; getToken: GetToken; }' is not assignable to type '{ isLoaded: true; isSignedIn: true; userId: string; sessionId: string; actor: ActJWTClaim; signOut: SignOut; getToken: GetToken; }'.
            Types of property 'isSignedIn' are incompatible.
              Type 'false' is not assignable to type 'true'.


/**
* Returns the current auth state, the user and session ids and the `getToken`
Expand Down Expand Up @@ -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 {
Expand All @@ -91,7 +94,7 @@ export const useAuth: UseAuth = () => {
actor: null,
signOut,
getToken,
};
} as InitReturn;
}

if (sessionId === null && userId === null) {
Expand All @@ -103,7 +106,7 @@ export const useAuth: UseAuth = () => {
actor: null,
signOut,
getToken,
};
} as LoadedReturn;
}

if (!!sessionId && !!userId) {
Expand All @@ -115,7 +118,7 @@ export const useAuth: UseAuth = () => {
actor,
signOut,
getToken,
};
} as UserReturn;
}

throw new Error(invalidStateError);
Expand Down