): JSX.Element => {
+ useAssertWrappedByClerkProvider('MultisessionAppSupport');
+
const session = useSessionContext();
return {children};
};
diff --git a/packages/react/src/components/withClerk.tsx b/packages/react/src/components/withClerk.tsx
index 1048b154d82..c8f0a0f5668 100644
--- a/packages/react/src/components/withClerk.tsx
+++ b/packages/react/src/components/withClerk.tsx
@@ -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 = (
@@ -13,6 +14,8 @@ export const withClerk =
(
displayName = displayName || Component.displayName || Component.name || 'Component';
Component.displayName = displayName;
const HOC = (props: Without
) => {
+ useAssertWrappedByClerkProvider(displayName || 'withClerk');
+
const clerk = useIsomorphicClerkContext();
if (!clerk.loaded) {
diff --git a/packages/react/src/hooks/useAssertWrappedByClerkProvider.ts b/packages/react/src/hooks/useAssertWrappedByClerkProvider.ts
new file mode 100644
index 00000000000..1fad233ec66
--- /dev/null
+++ b/packages/react/src/hooks/useAssertWrappedByClerkProvider.ts
@@ -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 });
+ });
+};
diff --git a/packages/react/src/hooks/useAuth.ts b/packages/react/src/hooks/useAuth.ts
index b8e60b3d1e6..661d69b5778 100644
--- a/packages/react/src/hooks/useAuth.ts
+++ b/packages/react/src/hooks/useAuth.ts
@@ -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;
@@ -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();
diff --git a/packages/react/src/hooks/useSignIn.ts b/packages/react/src/hooks/useSignIn.ts
index 73885d47cc5..d666940dd84 100644
--- a/packages/react/src/hooks/useSignIn.ts
+++ b/packages/react/src/hooks/useSignIn.ts
@@ -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 =
| {
@@ -18,6 +19,8 @@ type UseSignInReturn =
type UseSignIn = () => UseSignInReturn;
export const useSignIn: UseSignIn = () => {
+ useAssertWrappedByClerkProvider('useSignIn');
+
const isomorphicClerk = useIsomorphicClerkContext();
const client = useClientContext();
diff --git a/packages/react/src/hooks/useSignUp.ts b/packages/react/src/hooks/useSignUp.ts
index 1398773b2a9..5b169c2779a 100644
--- a/packages/react/src/hooks/useSignUp.ts
+++ b/packages/react/src/hooks/useSignUp.ts
@@ -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 =
| {
@@ -18,6 +19,8 @@ type UseSignUpReturn =
type UseSignUp = () => UseSignUpReturn;
export const useSignUp: UseSignUp = () => {
+ useAssertWrappedByClerkProvider('useSignUp');
+
const isomorphicClerk = useIsomorphicClerkContext();
const client = useClientContext();
diff --git a/packages/shared/src/error.ts b/packages/shared/src/error.ts
index 8f35f232a63..6cf10ef7f6e 100644
--- a/packages/shared/src/error.ts
+++ b/packages/shared/src/error.ts
@@ -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 component. Learn more: https://clerk.com/docs/components/clerk-provider`,
});
type MessageKeys = keyof typeof DefaultMessages;
@@ -213,6 +214,9 @@ export interface ErrorThrower {
throwInvalidProxyUrl(params: { url?: string }): never;
throwMissingPublishableKeyError(): never;
+
+ throwMissingClerkProviderError(params: { source?: string }): never;
+
throw(message: string): never;
}
@@ -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));
},
diff --git a/packages/shared/src/react/contexts.tsx b/packages/shared/src/react/contexts.tsx
index de372140a89..51b9bab76db 100644
--- a/packages/shared/src/react/contexts.tsx
+++ b/packages/shared/src/react/contexts.tsx
@@ -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 component. Learn more: https://clerk.com/docs/components/clerk-provider`,
+ );
+ }
+}
+
export {
ClientContext,
useClientContext,
@@ -61,4 +76,5 @@ export {
useSessionContext,
ClerkInstanceContext,
useClerkInstanceContext,
+ useAssertWrappedByClerkProvider,
};
diff --git a/packages/shared/src/react/hooks/useClerk.ts b/packages/shared/src/react/hooks/useClerk.ts
index 505cab5e80a..c65bd45a0e9 100644
--- a/packages/shared/src/react/hooks/useClerk.ts
+++ b/packages/shared/src/react/hooks/useClerk.ts
@@ -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();
+};
diff --git a/packages/shared/src/react/hooks/useOrganization.tsx b/packages/shared/src/react/hooks/useOrganization.tsx
index 483fb158458..044f1461859 100644
--- a/packages/shared/src/react/hooks/useOrganization.tsx
+++ b/packages/shared/src/react/hooks/useOrganization.tsx
@@ -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';
@@ -110,6 +115,9 @@ export const useOrganization: UseOrganization = params => {
memberships: membersListParams,
invitations: invitationsListParams,
} = params || {};
+
+ useAssertWrappedByClerkProvider('useOrganization');
+
const { organization } = useOrganizationContext();
const session = useSessionContext();
diff --git a/packages/shared/src/react/hooks/useOrganizationList.tsx b/packages/shared/src/react/hooks/useOrganizationList.tsx
index 80dc52e0af4..2f879a92d38 100644
--- a/packages/shared/src/react/hooks/useOrganizationList.tsx
+++ b/packages/shared/src/react/hooks/useOrganizationList.tsx
@@ -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';
@@ -85,6 +85,8 @@ type UseOrganizationList = (
export const useOrganizationList: UseOrganizationList = params => {
const { userMemberships, userInvitations, userSuggestions } = params || {};
+ useAssertWrappedByClerkProvider('useOrganizationList');
+
const userMembershipsSafeValues = useWithSafeValues(userMemberships, {
initialPage: 1,
pageSize: 10,
diff --git a/packages/shared/src/react/hooks/useSession.ts b/packages/shared/src/react/hooks/useSession.ts
index db27bbbaf34..6f9544483ec 100644
--- a/packages/shared/src/react/hooks/useSession.ts
+++ b/packages/shared/src/react/hooks/useSession.ts
@@ -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 }
@@ -30,6 +30,8 @@ type UseSession = () => UseSessionReturn;
* }
*/
export const useSession: UseSession = () => {
+ useAssertWrappedByClerkProvider('useSession');
+
const session = useSessionContext();
if (session === undefined) {
diff --git a/packages/shared/src/react/hooks/useSessionList.ts b/packages/shared/src/react/hooks/useSessionList.ts
index 03f2cdcdbf4..32e9c3718d7 100644
--- a/packages/shared/src/react/hooks/useSessionList.ts
+++ b/packages/shared/src/react/hooks/useSessionList.ts
@@ -1,6 +1,6 @@
import type { SessionResource, SetActive } from '@clerk/types';
-import { useClerkInstanceContext, useClientContext } from '../contexts';
+import { useAssertWrappedByClerkProvider, useClerkInstanceContext, useClientContext } from '../contexts';
type UseSessionListReturn =
| {
@@ -17,6 +17,8 @@ type UseSessionListReturn =
type UseSessionList = () => UseSessionListReturn;
export const useSessionList: UseSessionList = () => {
+ useAssertWrappedByClerkProvider('useSessionList');
+
const isomorphicClerk = useClerkInstanceContext();
const client = useClientContext();
diff --git a/packages/shared/src/react/hooks/useUser.ts b/packages/shared/src/react/hooks/useUser.ts
index 89a0934de1b..a1bc3686738 100644
--- a/packages/shared/src/react/hooks/useUser.ts
+++ b/packages/shared/src/react/hooks/useUser.ts
@@ -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 }
@@ -28,6 +28,8 @@ type UseUserReturn =
* }
*/
export function useUser(): UseUserReturn {
+ useAssertWrappedByClerkProvider('useUser');
+
const user = useUserContext();
if (user === undefined) {
diff --git a/packages/shared/src/react/index.ts b/packages/shared/src/react/index.ts
index 1a4c5968064..40735d10f2d 100644
--- a/packages/shared/src/react/index.ts
+++ b/packages/shared/src/react/index.ts
@@ -11,4 +11,5 @@ export {
UserContext,
useSessionContext,
useUserContext,
+ useAssertWrappedByClerkProvider,
} from './contexts';