Skip to content
Merged
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
11 changes: 11 additions & 0 deletions .changeset/slow-bugs-exist.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
'@clerk/clerk-js': patch
'@clerk/shared': patch
'@clerk/clerk-react': patch
---

Add `useAssertWrappedByClerkProvider` to internal code. If you use hooks like `useAuth` outside of the `<ClerkProvider />` context an error will be thrown. For example:

```shell
@clerk/clerk-react: useAuth can only be used within the <ClerkProvider /> component
```
14 changes: 14 additions & 0 deletions packages/react/src/components/controlComponents.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,10 +11,13 @@ import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { useSessionContext } from '../contexts/SessionContext';
import { LoadedGuarantee } from '../contexts/StructureContext';
import { useAuth } from '../hooks';
import { useAssertWrappedByClerkProvider } from '../hooks/useAssertWrappedByClerkProvider';
import type { RedirectToSignInProps, RedirectToSignUpProps, WithClerkProp } from '../types';
import { withClerk } from './withClerk';

export const SignedIn = ({ children }: React.PropsWithChildren<unknown>): JSX.Element | null => {
useAssertWrappedByClerkProvider('SignedIn');

const { userId } = useAuthContext();
if (userId) {
return <>{children}</>;
Expand All@@ -23,6 +26,8 @@ export const SignedIn = ({ children }: React.PropsWithChildren<unknown>): JSX.El
};

export const SignedOut = ({ children }: React.PropsWithChildren<unknown>): JSX.Element | null => {
useAssertWrappedByClerkProvider('SignedOut');

const { userId } = useAuthContext();
if (userId === null) {
return <>{children}</>;
Expand All@@ -31,6 +36,8 @@ export const SignedOut = ({ children }: React.PropsWithChildren<unknown>): JSX.E
};

export const ClerkLoaded = ({ children }: React.PropsWithChildren<unknown>): JSX.Element | null => {
useAssertWrappedByClerkProvider('ClerkLoaded');

const isomorphicClerk = useIsomorphicClerkContext();
if (!isomorphicClerk.loaded) {
return null;
Expand All@@ -39,6 +46,8 @@ export const ClerkLoaded = ({ children }: React.PropsWithChildren<unknown>): JSX
};

export const ClerkLoading = ({ children }: React.PropsWithChildren<unknown>): JSX.Element | null => {
useAssertWrappedByClerkProvider('ClerkLoading');

const isomorphicClerk = useIsomorphicClerkContext();
if (isomorphicClerk.loaded) {
return null;
Expand DownExpand Up@@ -86,6 +95,8 @@ type ProtectProps = React.PropsWithChildren<
* ```
*/
export const Protect = ({ children, fallback, ...restAuthorizedParams }: ProtectProps) => {
useAssertWrappedByClerkProvider('Protect');

const { isLoaded, has, userId } = useAuth();

/**
Expand DownExpand Up@@ -129,6 +140,7 @@ export const Protect = ({ children, fallback, ...restAuthorizedParams }: Protect
*/
return authorized;
};
/* eslint-enable react-hooks/rules-of-hooks */

export const RedirectToSignIn = withClerk(({ clerk, ...props }: WithClerkProp<RedirectToSignInProps>) => {
const { client, session } = clerk;
Expand DownExpand Up@@ -193,6 +205,8 @@ export const AuthenticateWithRedirectCallback = withClerk(
);

export const MultisessionAppSupport = ({ children }: React.PropsWithChildren<unknown>): JSX.Element => {
useAssertWrappedByClerkProvider('MultisessionAppSupport');

const session = useSessionContext();
return <React.Fragment key={session ? session.id : 'no-users'}>{children}</React.Fragment>;
};
3 changes: 3 additions & 0 deletions packages/react/src/components/withClerk.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import React from 'react';
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { LoadedGuarantee } from '../contexts/StructureContext';
import { hocChildrenNotAFunctionError } from '../errors';
import { useAssertWrappedByClerkProvider } from '../hooks/useAssertWrappedByClerkProvider';
import { errorThrower } from '../utils';

export const withClerk = <P extends { clerk: LoadedClerk }>(
Expand All@@ -13,6 +14,8 @@ export const withClerk = <P extends { clerk: LoadedClerk }>(
displayName = displayName || Component.displayName || Component.name || 'Component';
Component.displayName = displayName;
const HOC = (props: Without<P, 'clerk'>) => {
useAssertWrappedByClerkProvider(displayName || 'withClerk');

const clerk = useIsomorphicClerkContext();

if (!clerk.loaded) {
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/hooks/useAssertWrappedByClerkProvider.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
import { useAssertWrappedByClerkProvider as useSharedAssertWrappedByClerkProvider } from '@clerk/shared/react';

import { errorThrower } from '../utils';

export const useAssertWrappedByClerkProvider = (source: string): void => {
useSharedAssertWrappedByClerkProvider(() => {
errorThrower.throwMissingClerkProviderError({ source });
});
};
3 changes: 3 additions & 0 deletions packages/react/src/hooks/useAuth.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import { useAuthContext } from '../contexts/AuthContext';
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { invalidStateError, useAuthHasRequiresRoleOrPermission } from '../errors';
import { errorThrower } from '../utils';
import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvider';
import { createGetToken, createSignOut } from './utils';

type CheckAuthorizationSignedOut = undefined;
Expand DownExpand Up@@ -109,6 +110,8 @@ type UseAuth = () => UseAuthReturn;
* }
*/
export const useAuth: UseAuth = () => {
useAssertWrappedByClerkProvider('useAuth');

const { sessionId, userId, actor, orgId, orgRole, orgSlug, orgPermissions } = useAuthContext();
const isomorphicClerk = useIsomorphicClerkContext();

Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/hooks/useSignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { useClientContext } from '@clerk/shared/react';
import type { SetActive, SignInResource } from '@clerk/types';

import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvider';

type UseSignInReturn =
| {
Expand All@@ -18,6 +19,8 @@ type UseSignInReturn =
type UseSignIn = () => UseSignInReturn;

export const useSignIn: UseSignIn = () => {
useAssertWrappedByClerkProvider('useSignIn');

const isomorphicClerk = useIsomorphicClerkContext();
const client = useClientContext();

Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/hooks/useSignUp.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { useClientContext } from '@clerk/shared/react';
import type { SetActive, SignUpResource } from '@clerk/types';

import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvider';

type UseSignUpReturn =
| {
Expand All@@ -18,6 +19,8 @@ type UseSignUpReturn =
type UseSignUp = () => UseSignUpReturn;

export const useSignUp: UseSignUp = () => {
useAssertWrappedByClerkProvider('useSignUp');

const isomorphicClerk = useIsomorphicClerkContext();
const client = useClientContext();

Expand Down
8 changes: 8 additions & 0 deletions packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -190,6 +190,7 @@ const DefaultMessages = Object.freeze({
InvalidProxyUrlErrorMessage: `The proxyUrl passed to Clerk is invalid. The expected value for proxyUrl is an absolute URL or a relative path with a leading '/'. (key={{url}})`,
InvalidPublishableKeyErrorMessage: `The publishableKey passed to Clerk is invalid. You can get your Publishable key at https://dashboard.clerk.com/last-active?path=api-keys. (key={{key}})`,
MissingPublishableKeyErrorMessage: `Missing publishableKey. You can get your key at https://dashboard.clerk.com/last-active?path=api-keys.`,
MissingClerkProvider: `{{source}} can only be used within the <ClerkProvider /> component. Learn more: https://clerk.com/docs/components/clerk-provider`,
});

type MessageKeys = keyof typeof DefaultMessages;
Expand All@@ -213,6 +214,9 @@ export interface ErrorThrower {
throwInvalidProxyUrl(params: { url?: string }): never;

throwMissingPublishableKeyError(): never;

throwMissingClerkProviderError(params: { source?: string }): never;

throw(message: string): never;
}

Expand DownExpand Up@@ -265,6 +269,10 @@ export function buildErrorThrower({ packageName, customMessages }: ErrorThrowerO
throw new Error(buildMessage(messages.MissingPublishableKeyErrorMessage));
},

throwMissingClerkProviderError(params: { source?: string }): never {
throw new Error(buildMessage(messages.MissingClerkProvider, params));
},

throw(message: string): never {
throw new Error(buildMessage(message));
},
Expand Down
16 changes: 16 additions & 0 deletions packages/shared/src/react/contexts.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,21 @@ const OrganizationProvider = ({
);
};

function useAssertWrappedByClerkProvider(displayNameOrFn: string | (() => void)): void {
const ctx = React.useContext(ClerkInstanceContext);

if (!ctx) {
if (typeof displayNameOrFn === 'function') {
displayNameOrFn();
return;
}

throw new Error(
`${displayNameOrFn} can only be used within the <ClerkProvider /> component. Learn more: https://clerk.com/docs/components/clerk-provider`,
);
}
Comment thread
tmilewski marked this conversation as resolved.
}

export {
ClientContext,
useClientContext,
Expand All@@ -61,4 +76,5 @@ export {
useSessionContext,
ClerkInstanceContext,
useClerkInstanceContext,
useAssertWrappedByClerkProvider,
};
7 changes: 5 additions & 2 deletions packages/shared/src/react/hooks/useClerk.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
import type { LoadedClerk } from '@clerk/types';

import { useClerkInstanceContext } from '../contexts';
import { useAssertWrappedByClerkProvider, useClerkInstanceContext } from '../contexts';

export const useClerk: () => LoadedClerk = useClerkInstanceContext;
export const useClerk = (): LoadedClerk => {
useAssertWrappedByClerkProvider('useClerk');
return useClerkInstanceContext();
};
10 changes: 9 additions & 1 deletion packages/shared/src/react/hooks/useOrganization.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,12 @@ import type {
OrganizationResource,
} from '@clerk/types';

import { useClerkInstanceContext, useOrganizationContext, useSessionContext } from '../contexts';
import {
useAssertWrappedByClerkProvider,
useClerkInstanceContext,
useOrganizationContext,
useSessionContext,
} from '../contexts';
import type { PaginatedResources, PaginatedResourcesWithDefault } from '../types';
import { usePagesOrInfinite, useWithSafeValues } from './usePagesOrInfinite';

Expand DownExpand Up@@ -110,6 +115,9 @@ export const useOrganization: UseOrganization = params => {
memberships: membersListParams,
invitations: invitationsListParams,
} = params || {};

useAssertWrappedByClerkProvider('useOrganization');

const { organization } = useOrganizationContext();
const session = useSessionContext();

Expand Down
4 changes: 3 additions & 1 deletion packages/shared/src/react/hooks/useOrganizationList.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ import type {
UserOrganizationInvitationResource,
} from '@clerk/types';

import { useClerkInstanceContext, useUserContext } from '../contexts';
import { useAssertWrappedByClerkProvider, useClerkInstanceContext, useUserContext } from '../contexts';
import type { PaginatedResources, PaginatedResourcesWithDefault } from '../types';
import { usePagesOrInfinite, useWithSafeValues } from './usePagesOrInfinite';

Expand DownExpand Up@@ -85,6 +85,8 @@ type UseOrganizationList = <T extends UseOrganizationListParams>(
export const useOrganizationList: UseOrganizationList = params => {
const { userMemberships, userInvitations, userSuggestions } = params || {};

useAssertWrappedByClerkProvider('useOrganizationList');

const userMembershipsSafeValues = useWithSafeValues(userMemberships, {
initialPage: 1,
pageSize: 10,
Expand Down
4 changes: 3 additions & 1 deletion packages/shared/src/react/hooks/useSession.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { ActiveSessionResource } from '@clerk/types';

import { useSessionContext } from '../contexts';
import { useAssertWrappedByClerkProvider, useSessionContext } from '../contexts';

type UseSessionReturn =
| { isLoaded: false; isSignedIn: undefined; session: undefined }
Expand DownExpand Up@@ -30,6 +30,8 @@ type UseSession = () => UseSessionReturn;
* }
*/
export const useSession: UseSession = () => {
useAssertWrappedByClerkProvider('useSession');

const session = useSessionContext();

if (session === undefined) {
Expand Down
4 changes: 3 additions & 1 deletion packages/shared/src/react/hooks/useSessionList.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { SessionResource, SetActive } from '@clerk/types';

import { useClerkInstanceContext, useClientContext } from '../contexts';
import { useAssertWrappedByClerkProvider, useClerkInstanceContext, useClientContext } from '../contexts';

type UseSessionListReturn =
| {
Expand All@@ -17,6 +17,8 @@ type UseSessionListReturn =
type UseSessionList = () => UseSessionListReturn;

export const useSessionList: UseSessionList = () => {
useAssertWrappedByClerkProvider('useSessionList');

const isomorphicClerk = useClerkInstanceContext();
const client = useClientContext();

Expand Down
4 changes: 3 additions & 1 deletion packages/shared/src/react/hooks/useUser.ts
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import type { UserResource } from '@clerk/types';

import { useUserContext } from '../contexts';
import { useAssertWrappedByClerkProvider, useUserContext } from '../contexts';

type UseUserReturn =
| { isLoaded: false; isSignedIn: undefined; user: undefined }
Expand DownExpand Up@@ -28,6 +28,8 @@ type UseUserReturn =
* }
*/
export function useUser(): UseUserReturn {
useAssertWrappedByClerkProvider('useUser');

const user = useUserContext();

if (user === undefined) {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/react/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,4 +11,5 @@ export {
UserContext,
useSessionContext,
useUserContext,
useAssertWrappedByClerkProvider,
} from './contexts';