Skip to content

Commit

Permalink
[SDK-805] Use the errorThrower shared util when throwing errors (#1999
Browse files Browse the repository at this point in the history
)

* refactor(clerk-react): Use the errorThrower shared util when throwing errors

* chore(clerk-react): Add changeset

* chore(clerk-react): Update packages/react/src/utils/useMaxAllowedInstancesGuard.tsx

Co-authored-by: panteliselef <panteliselef@outlook.com>

---------

Co-authored-by: panteliselef <panteliselef@outlook.com>
  • Loading branch information
anagstef and panteliselef committed Nov 9, 2023
1 parent 83562b8 commit e0e79b4
Show file tree
Hide file tree
Showing 13 changed files with 46 additions and 30 deletions.
6 changes: 6 additions & 0 deletions .changeset/tough-pots-grow.md
@@ -0,0 +1,6 @@
---
'@clerk/shared': patch
'@clerk/clerk-react': patch
---

Use the errorThrower shared utility when throwing errors
3 changes: 2 additions & 1 deletion packages/react/src/components/withClerk.tsx
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 { errorThrower } from '../utils';

export const withClerk = <P extends { clerk: LoadedClerk }>(
Component: React.ComponentType<P>,
Expand Down Expand Up @@ -37,7 +38,7 @@ export const WithClerk: React.FC<{
const clerk = useIsomorphicClerkContext();

if (typeof children !== 'function') {
throw new Error(hocChildrenNotAFunctionError);
errorThrower.throw(hocChildrenNotAFunctionError);
}

if (!clerk.loaded) {
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/withSession.tsx
Expand Up @@ -3,6 +3,7 @@ import React from 'react';

import { useSessionContext } from '../contexts/SessionContext';
import { hocChildrenNotAFunctionError } from '../errors';
import { errorThrower } from '../utils';

export const withSession = <P extends { session: SessionResource }>(
Component: React.ComponentType<P>,
Expand Down Expand Up @@ -35,7 +36,7 @@ export const WithSession: React.FC<{
const session = useSessionContext();

if (typeof children !== 'function') {
throw new Error(hocChildrenNotAFunctionError);
errorThrower.throw(hocChildrenNotAFunctionError);
}

if (!session) {
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/withUser.tsx
Expand Up @@ -3,6 +3,7 @@ import React from 'react';

import { useUserContext } from '../contexts/UserContext';
import { hocChildrenNotAFunctionError } from '../errors';
import { errorThrower } from '../utils';

export const withUser = <P extends { user: UserResource }>(Component: React.ComponentType<P>, displayName?: string) => {
displayName = displayName || Component.displayName || Component.name || 'Component';
Expand Down Expand Up @@ -32,7 +33,7 @@ export const WithUser: React.FC<{
const user = useUserContext();

if (typeof children !== 'function') {
throw new Error(hocChildrenNotAFunctionError);
errorThrower.throw(hocChildrenNotAFunctionError);
}

if (!user) {
Expand Down
5 changes: 3 additions & 2 deletions packages/react/src/contexts/assertHelpers.ts
@@ -1,13 +1,14 @@
import { noClerkProviderError, noGuaranteedLoadedError } from '../errors';
import { errorThrower } from '../utils';

export function assertWrappedByClerkProvider(contextVal: unknown): asserts contextVal {
if (!contextVal) {
throw new Error(noClerkProviderError);
errorThrower.throw(noClerkProviderError);
}
}

export function assertClerkLoadedGuarantee(guarantee: unknown, hookName: string): asserts guarantee {
if (!guarantee) {
throw new Error(noGuaranteedLoadedError(hookName));
errorThrower.throw(noGuaranteedLoadedError(hookName));
}
}
31 changes: 14 additions & 17 deletions packages/react/src/errors.ts
Expand Up @@ -6,43 +6,40 @@ export {
isMetamaskError,
} from '@clerk/shared/error';

export const noClerkProviderError = 'Clerk: You must wrap your application in a <ClerkProvider> component.';
export const noClerkProviderError = 'You must wrap your application in a <ClerkProvider> component.';

export const noGuaranteedLoadedError = (hookName: string) =>
`Clerk: You're calling ${hookName} before there's a guarantee the client has been loaded. Call ${hookName} from a child of <SignedIn>, <SignedOut>, or <ClerkLoaded>, or use the withClerk() HOC.`;

export const noGuaranteedUserError = (hookName: string) =>
`Clerk: You're calling ${hookName} before there's a guarantee there's an active user. Call ${hookName} from a child of <SignedIn> or use the withUser() HOC.`;
`You're calling ${hookName} before there's a guarantee the client has been loaded. Call ${hookName} from a child of <SignedIn>, <SignedOut>, or <ClerkLoaded>, or use the withClerk() HOC.`;

export const multipleClerkProvidersError =
"Clerk: You've added multiple <ClerkProvider> components in your React component tree. Wrap your components in a single <ClerkProvider>.";
"You've added multiple <ClerkProvider> components in your React component tree. Wrap your components in a single <ClerkProvider>.";

export const hocChildrenNotAFunctionError = 'Clerk: Child of WithClerk must be a function.';
export const hocChildrenNotAFunctionError = 'Child of WithClerk must be a function.';

export const multipleChildrenInButtonComponent = (name: string) =>
`Clerk: You've passed multiple children components to <${name}/>. You can only pass a single child component or text.`;
`You've passed multiple children components to <${name}/>. You can only pass a single child component or text.`;

export const invalidStateError =
'Clerk: Invalid state. Feel free to submit a bug or reach out to support here: https://clerk.com/support';
'Invalid state. Feel free to submit a bug or reach out to support here: https://clerk.com/support';

export const unsupportedNonBrowserDomainOrProxyUrlFunction =
'Clerk: Unsupported usage of domain or proxyUrl. The usage of domain or proxyUrl as function is not supported in non-browser environments.';
'Unsupported usage of domain or proxyUrl. The usage of domain or proxyUrl as function is not supported in non-browser environments.';

export const userProfilePageRenderedError =
'Clerk: <UserProfile.Page /> component needs to be a direct child of `<UserProfile />` or `<UserButton />`.';
'<UserProfile.Page /> component needs to be a direct child of `<UserProfile />` or `<UserButton />`.';
export const userProfileLinkRenderedError =
'Clerk: <UserProfile.Link /> component needs to be a direct child of `<UserProfile />` or `<UserButton />`.';
'<UserProfile.Link /> component needs to be a direct child of `<UserProfile />` or `<UserButton />`.';

export const organizationProfilePageRenderedError =
'Clerk: <OrganizationProfile.Page /> component needs to be a direct child of `<OrganizationProfile />` or `<OrganizationSwitcher />`.';
'<OrganizationProfile.Page /> component needs to be a direct child of `<OrganizationProfile />` or `<OrganizationSwitcher />`.';
export const organizationProfileLinkRenderedError =
'Clerk: <OrganizationProfile.Link /> component needs to be a direct child of `<OrganizationProfile />` or `<OrganizationSwitcher />`.';
'<OrganizationProfile.Link /> component needs to be a direct child of `<OrganizationProfile />` or `<OrganizationSwitcher />`.';

export const customPagesIgnoredComponent = (componentName: string) =>
`Clerk: <${componentName} /> can only accept <${componentName}.Page /> and <${componentName}.Link /> as its children. Any other provided component will be ignored.`;
`<${componentName} /> can only accept <${componentName}.Page /> and <${componentName}.Link /> as its children. Any other provided component will be ignored.`;

export const customPageWrongProps = (componentName: string) =>
`Clerk: Missing props. <${componentName}.Page /> component requires the following props: url, label, labelIcon, alongside with children to be rendered inside the page.`;
`Missing props. <${componentName}.Page /> component requires the following props: url, label, labelIcon, alongside with children to be rendered inside the page.`;

export const customLinkWrongProps = (componentName: string) =>
`Clerk: Missing props. <${componentName}.Link /> component requires the following props: url, label and labelIcon.`;
`Missing props. <${componentName}.Link /> component requires the following props: url, label and labelIcon.`;
3 changes: 2 additions & 1 deletion packages/react/src/hooks/useAuth.ts
Expand Up @@ -11,6 +11,7 @@ import { useAuthContext } from '../contexts/AuthContext';
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
import { invalidStateError } from '../errors';
import type IsomorphicClerk from '../isomorphicClerk';
import { errorThrower } from '../utils';
import { createGetToken, createSignOut } from './utils';

type experimental__CheckAuthorizationSignedOut = (
Expand Down Expand Up @@ -213,5 +214,5 @@ export const useAuth: UseAuth = () => {
};
}

throw new Error(invalidStateError);
return errorThrower.throw(invalidStateError);
};
6 changes: 3 additions & 3 deletions packages/react/src/isomorphicClerk.ts
Expand Up @@ -39,7 +39,7 @@ import type {
HeadlessBrowserClerkConstrutor,
IsomorphicClerkOptions,
} from './types';
import { isConstructor, loadClerkJsScript } from './utils';
import { errorThrower, isConstructor, loadClerkJsScript } from './utils';

export interface Global {
Clerk?: HeadlessBrowserClerk | BrowserClerk;
Expand Down Expand Up @@ -105,7 +105,7 @@ export default class IsomorphicClerk {
return handleValueOrFn(this.#domain, new URL(window.location.href), '');
}
if (typeof this.#domain === 'function') {
throw new Error(unsupportedNonBrowserDomainOrProxyUrlFunction);
return errorThrower.throw(unsupportedNonBrowserDomainOrProxyUrlFunction);
}
return this.#domain || '';
}
Expand All @@ -117,7 +117,7 @@ export default class IsomorphicClerk {
return handleValueOrFn(this.#proxyUrl, new URL(window.location.href), '');
}
if (typeof this.#proxyUrl === 'function') {
throw new Error(unsupportedNonBrowserDomainOrProxyUrlFunction);
return errorThrower.throw(unsupportedNonBrowserDomainOrProxyUrlFunction);
}
return this.#proxyUrl || '';
}
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/utils/childrenUtils.tsx
@@ -1,14 +1,15 @@
import React from 'react';

import { multipleChildrenInButtonComponent } from '../errors';
import { errorThrower } from './errorThrower';

export const assertSingleChild =
(children: React.ReactNode) =>
(name: 'SignInButton' | 'SignUpButton' | 'SignOutButton' | 'SignInWithMetamaskButton') => {
try {
return React.Children.only(children);
} catch (e) {
throw new Error(multipleChildrenInButtonComponent(name));
return errorThrower.throw(multipleChildrenInButtonComponent(name));
}
};

Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/utils/errorThrower.ts
@@ -1,7 +1,7 @@
import type { ErrorThrowerOptions } from '@clerk/shared/error';
import { buildErrorThrower } from '@clerk/shared/error';

const errorThrower = buildErrorThrower({ packageName: '@clerk/react' });
const errorThrower = buildErrorThrower({ packageName: '@clerk/clerk-react' });

function __internal__setErrorThrowerOptions(options: ErrorThrowerOptions) {
errorThrower.setMessages(options).setPackageName(options);
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/utils/useMaxAllowedInstancesGuard.tsx
@@ -1,12 +1,14 @@
import React from 'react';

import { errorThrower } from './errorThrower';

const counts = new Map<string, number>();

export function useMaxAllowedInstancesGuard(name: string, error: string, maxCount = 1): void {
React.useEffect(() => {
const count = counts.get(name) || 0;
if (count == maxCount) {
throw new Error(error);
return errorThrower.throw(error);
}
counts.set(name, count + 1);

Expand Down
5 changes: 5 additions & 0 deletions packages/shared/src/error.ts
Expand Up @@ -256,6 +256,7 @@ export interface ErrorThrower {
throwInvalidProxyUrl(params: { url?: string }): never;

throwMissingPublishableKeyError(): never;
throw(message: string): never;
}

export function buildErrorThrower({ packageName, customMessages }: ErrorThrowerOptions): ErrorThrower {
Expand Down Expand Up @@ -310,5 +311,9 @@ export function buildErrorThrower({ packageName, customMessages }: ErrorThrowerO
throwMissingPublishableKeyError(): never {
throw new Error(buildMessage(messages.MissingPublishableKeyErrorMessage));
},

throw(message: string): never {
throw new Error(buildMessage(message));
},
};
}
2 changes: 1 addition & 1 deletion packages/shared/src/utils/logErrorInDevMode.ts
Expand Up @@ -2,6 +2,6 @@ import { isDevelopmentEnvironment } from './runtimeEnvironment';

export const logErrorInDevMode = (message: string) => {
if (isDevelopmentEnvironment()) {
console.error(message);
console.error(`Clerk: ${message}`);
}
};

0 comments on commit e0e79b4

Please sign in to comment.