From f5d5a8166ec01faacc8c120d80c2e75c73bfabaf Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Mon, 4 Nov 2024 10:19:02 -0800
Subject: [PATCH 1/5] wip
---
.../src/ui/components/SignIn/SignIn.tsx | 6 +-
.../ui/contexts/ClerkUIComponentsContext.tsx | 29 ++++++++--
packages/clerk-js/src/ui/portal/index.tsx | 57 ++++++++++---------
.../src/ui/utils/test/createFixtures.tsx | 15 ++++-
4 files changed, 69 insertions(+), 38 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index 0f4287db471..1229b10dcf5 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -3,7 +3,7 @@ import type { SignInModalProps, SignInProps } from '@clerk/types';
import React from 'react';
import { SignInEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
-import { ComponentContext, useSignInContext, withCoreSessionSwitchGuard } from '../../contexts';
+import { SignInContext, useSignInContext, withCoreSessionSwitchGuard } from '../../contexts';
import { Flow } from '../../customizables';
import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
import { ResetPassword } from './ResetPassword';
@@ -85,7 +85,7 @@ export const SignInModal = (props: SignInModalProps): JSX.Element => {
return (
- {
routing='virtual'
/>
-
+
);
};
diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
index 14a42d66829..90a7ce67b05 100644
--- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
+++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
@@ -36,6 +36,21 @@ const populateParamFromObject = createDynamicParamParser({ regex: /:(\w+)/ });
export const ComponentContext = React.createContext(null);
+export function componentContextWrapper({ componentName }: { componentName: 'SignIn' }): typeof SignInContext;
+export function componentContextWrapper({ componentName }: { componentName: string }): typeof ComponentContext;
+export function componentContextWrapper({
+ componentName,
+}: {
+ componentName: string;
+}): typeof SignInContext | typeof ComponentContext {
+ switch (componentName) {
+ case 'SignIn':
+ return SignInContext;
+ default:
+ return ComponentContext;
+ }
+}
+
const getInitialValuesFromQueryParams = (queryString: string, params: string[]) => {
const props: Record = {};
const searchParams = new URLSearchParams(queryString);
@@ -131,14 +146,22 @@ export type SignInContextType = SignInCtx & {
transferable: boolean;
};
+export const SignInContext = React.createContext(null);
+
export const useSignInContext = (): SignInContextType => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as SignInCtx;
+ const context = React.useContext(SignInContext);
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
const { queryParams, queryString } = useRouter();
const options = useOptions();
const clerk = useClerk();
+ if (context === null || context.componentName !== 'SignIn') {
+ throw new Error(`Clerk: useSignInContext called outside of the mounted SignIn component.`);
+ }
+
+ const { componentName, ...ctx } = context;
+
const initialValuesFromQueryParams = useMemo(
() => getInitialValuesFromQueryParams(queryString, SIGN_IN_INITIAL_VALUE_KEYS),
[],
@@ -154,10 +177,6 @@ export const useSignInContext = (): SignInContextType => {
queryParams,
);
- if (componentName !== 'SignIn') {
- throw new Error('Clerk: useSignInContext called outside of the mounted SignIn component.');
- }
-
const afterSignInUrl = clerk.buildUrlWithAuth(redirectUrls.getAfterSignInUrl());
const afterSignUpUrl = clerk.buildUrlWithAuth(redirectUrls.getAfterSignUpUrl());
diff --git a/packages/clerk-js/src/ui/portal/index.tsx b/packages/clerk-js/src/ui/portal/index.tsx
index 426ade2f227..862ce3ce261 100644
--- a/packages/clerk-js/src/ui/portal/index.tsx
+++ b/packages/clerk-js/src/ui/portal/index.tsx
@@ -5,7 +5,7 @@ import ReactDOM from 'react-dom';
import { PRESERVED_QUERYSTRING_PARAMS } from '../../core/constants';
import { clerkErrorPathRouterMissingPath } from '../../core/errors';
import { normalizeRoutingOptions } from '../../utils/normalizeRoutingOptions';
-import { ComponentContext } from '../contexts';
+import { ComponentContext, componentContextWrapper } from '../contexts';
import { HashRouter, PathRouter, VirtualRouter } from '../router';
import type { AvailableComponentCtx } from '../types';
@@ -16,37 +16,40 @@ type PortalProps;
-export class Portal extends React.PureComponent> {
- render() {
- const { props, component, componentName, node } = this.props;
- const normalizedProps = { ...props, ...normalizeRoutingOptions({ routing: props?.routing, path: props?.path }) };
-
- const el = (
-
-
- {React.createElement(component, normalizedProps as PortalProps['props'])}
-
-
- );
+export function Portal({
+ props,
+ component,
+ componentName,
+ node,
+}: PortalProps) {
+ const normalizedProps = { ...props, ...normalizeRoutingOptions({ routing: props?.routing, path: props?.path }) };
- if (normalizedProps?.routing === 'path') {
- if (!normalizedProps?.path) {
- clerkErrorPathRouterMissingPath(componentName);
- }
+ const ComponentContextProvider = componentContextWrapper({ componentName });
+ const el = (
+
+
+ {React.createElement(component, normalizedProps as PortalProps['props'])}
+
+
+ );
- return ReactDOM.createPortal(
-
- {el}
- ,
- node,
- );
+ if (normalizedProps?.routing === 'path') {
+ if (!normalizedProps?.path) {
+ clerkErrorPathRouterMissingPath(componentName);
}
- return ReactDOM.createPortal({el}, node);
+ return ReactDOM.createPortal(
+
+ {el}
+ ,
+ node,
+ );
}
+
+ return ReactDOM.createPortal({el}, node);
}
type VirtualBodyRootPortalProps> = {
diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
index a1deee34314..303900c2a9d 100644
--- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
+++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
@@ -4,7 +4,13 @@ import React from 'react';
import { Clerk as ClerkCtor } from '../../../core/clerk';
import { Client, Environment } from '../../../core/resources';
-import { ComponentContext, CoreClerkContextWrapper, EnvironmentProvider, OptionsProvider } from '../../contexts';
+import {
+ ComponentContext,
+ componentContextWrapper,
+ CoreClerkContextWrapper,
+ EnvironmentProvider,
+ OptionsProvider,
+} from '../../contexts';
import { AppearanceProvider } from '../../customizables';
import { FlowMetadataProvider } from '../../elements';
import { RouteContext } from '../../router';
@@ -84,21 +90,24 @@ const unboundCreateFixtures = [
const MockClerkProvider = (props: any) => {
const { children } = props;
+
+ const ContextProvider = componentContextWrapper({ componentName });
return (
new Map() }}
>
+
-
+
{children}
-
+
From bd64602a8e4ab301981266cfa41325bb796d1af8 Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Mon, 4 Nov 2024 17:36:13 -0800
Subject: [PATCH 2/5] feat(clerk-js): Replace ComponentContext with
SignUpContext and UserProfileContext
---
.../src/ui/components/SignUp/SignUp.tsx | 6 ++--
.../ui/components/UserProfile/UserProfile.tsx | 6 ++--
.../ui/contexts/ClerkUIComponentsContext.tsx | 30 ++++++++++++++-----
3 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx
index 0ae292691c8..e231773fc81 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUp.tsx
@@ -3,7 +3,7 @@ import type { SignUpModalProps, SignUpProps } from '@clerk/types';
import React from 'react';
import { SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
-import { ComponentContext, useSignUpContext, withCoreSessionSwitchGuard } from '../../contexts';
+import { SignUpContext, useSignUpContext, withCoreSessionSwitchGuard } from '../../contexts';
import { Flow } from '../../customizables';
import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
import { SignUpContinue } from './SignUpContinue';
@@ -98,7 +98,7 @@ export const SignUpModal = (props: SignUpModalProps): JSX.Element => {
return (
- {
routing='virtual'
/>
-
+
);
};
diff --git a/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx b/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx
index 0eef00cec53..ad641d35a5a 100644
--- a/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx
+++ b/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx
@@ -1,7 +1,7 @@
import type { UserProfileModalProps, UserProfileProps } from '@clerk/types';
import React from 'react';
-import { ComponentContext, withCoreUserGuard } from '../../contexts';
+import { UserProfileContext, withCoreUserGuard } from '../../contexts';
import { Flow, localizationKeys } from '../../customizables';
import { NavbarMenuButtonRow, ProfileCard, withCardStateProvider } from '../../elements';
import { Route, Switch } from '../../router';
@@ -54,12 +54,12 @@ export const UserProfileModal = (props: UserProfileModalProps): JSX.Element => {
return (
-
+
{/*TODO: Used by InvisibleRootBox, can we simplify? */}
-
+
);
};
diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
index 0d4751c7ff4..1ce81c775b5 100644
--- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
+++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
@@ -38,15 +38,21 @@ const populateParamFromObject = createDynamicParamParser({ regex: /:(\w+)/ });
export const ComponentContext = React.createContext(null);
export function componentContextWrapper({ componentName }: { componentName: 'SignIn' }): typeof SignInContext;
+export function componentContextWrapper({ componentName }: { componentName: 'SignUp' }): typeof SignUpContext;
+export function componentContextWrapper({ componentName }: { componentName: 'UserProfile' }): typeof UserProfileContext;
export function componentContextWrapper({ componentName }: { componentName: string }): typeof ComponentContext;
export function componentContextWrapper({
componentName,
}: {
componentName: string;
-}): typeof SignInContext | typeof ComponentContext {
+}): typeof SignInContext | typeof SignUpContext | typeof UserProfileContext | typeof ComponentContext {
switch (componentName) {
case 'SignIn':
return SignInContext;
+ case 'SignUp':
+ return SignUpContext;
+ case 'UserProfile':
+ return UserProfileContext;
default:
return ComponentContext;
}
@@ -76,8 +82,10 @@ export type SignUpContextType = SignUpCtx & {
waitlistUrl: string;
};
+export const SignUpContext = React.createContext(null);
+
export const useSignUpContext = (): SignUpContextType => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as SignUpCtx;
+ const context = React.useContext(SignUpContext);
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
const { queryParams, queryString } = useRouter();
@@ -89,6 +97,12 @@ export const useSignUpContext = (): SignUpContextType => {
[],
);
+ if (!context || context.componentName !== 'SignUp') {
+ throw new Error('Clerk: useSignUpContext called outside of the mounted SignUp component.');
+ }
+
+ const { componentName, ...ctx } = context;
+
const redirectUrls = new RedirectUrls(
options,
{
@@ -99,10 +113,6 @@ export const useSignUpContext = (): SignUpContextType => {
queryParams,
);
- if (componentName !== 'SignUp') {
- throw new Error('Clerk: useSignUpContext called outside of the mounted SignUp component.');
- }
-
const afterSignUpUrl = clerk.buildUrlWithAuth(redirectUrls.getAfterSignUpUrl());
const afterSignInUrl = clerk.buildUrlWithAuth(redirectUrls.getAfterSignInUrl());
@@ -253,15 +263,19 @@ export type UserProfileContextType = UserProfileCtx & {
pages: PagesType;
};
+export const UserProfileContext = React.createContext(null);
+
export const useUserProfileContext = (): UserProfileContextType => {
- const { componentName, customPages, ...ctx } = (React.useContext(ComponentContext) || {}) as UserProfileCtx;
+ const context = React.useContext(UserProfileContext);
const { queryParams } = useRouter();
const clerk = useClerk();
- if (componentName !== 'UserProfile') {
+ if (!context || context.componentName !== 'UserProfile') {
throw new Error('Clerk: useUserProfileContext called outside of the mounted UserProfile component.');
}
+ const { componentName, customPages, ...ctx } = context;
+
const pages = useMemo(() => {
return createUserProfileCustomPages(customPages || [], clerk);
}, [customPages]);
From c0c85a17dce0a5717ff15df7a8bc868344446902 Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Mon, 4 Nov 2024 17:37:23 -0800
Subject: [PATCH 3/5] chore(repo): Add changeset
---
.changeset/poor-countries-count.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/poor-countries-count.md
diff --git a/.changeset/poor-countries-count.md b/.changeset/poor-countries-count.md
new file mode 100644
index 00000000000..55ffcf3a072
--- /dev/null
+++ b/.changeset/poor-countries-count.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Internal change to use component-specific context providers. This change does not impact consumers.
From 44e98f24f009d0a8efde8369de89071bf6921d08 Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Tue, 5 Nov 2024 14:47:20 -0800
Subject: [PATCH 4/5] feat(clerk-js): Replace ComponentContext with
component-specific contexts
---
.../CreateOrganization/CreateOrganization.tsx | 6 +-
.../OrganizationProfile.tsx | 6 +-
.../ui/components/UserVerification/index.tsx | 6 +-
.../src/ui/components/Waitlist/Waitlist.tsx | 6 +-
.../ui/contexts/ClerkUIComponentsContext.tsx | 147 ++++++++++++++----
packages/clerk-js/src/ui/portal/index.tsx | 23 +--
packages/clerk-js/src/ui/types.ts | 2 +
.../src/ui/utils/test/createFixtures.tsx | 34 ++--
8 files changed, 165 insertions(+), 65 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganization.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganization.tsx
index 559555c0bdf..214817620b6 100644
--- a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganization.tsx
+++ b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganization.tsx
@@ -1,6 +1,6 @@
import type { CreateOrganizationModalProps } from '@clerk/types';
-import { ComponentContext, withCoreUserGuard } from '../../contexts';
+import { CreateOrganizationContext, withCoreUserGuard } from '../../contexts';
import { Flow } from '../../customizables';
import { withCardStateProvider } from '../../elements';
import { Route, Switch } from '../../router';
@@ -37,11 +37,11 @@ export const CreateOrganizationModal = (props: CreateOrganizationModalProps): JS
return (
-
+
-
+
);
};
diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx
index e540dacf640..116eb1f5d4b 100644
--- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx
+++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx
@@ -2,7 +2,7 @@ import { useOrganization } from '@clerk/shared/react';
import type { OrganizationProfileModalProps, OrganizationProfileProps } from '@clerk/types';
import React from 'react';
-import { ComponentContext, withCoreUserGuard } from '../../contexts';
+import { OrganizationProfileContext, withCoreUserGuard } from '../../contexts';
import { Flow, localizationKeys } from '../../customizables';
import { NavbarMenuButtonRow, ProfileCard, withCardStateProvider } from '../../elements';
import { Route, Switch } from '../../router';
@@ -58,12 +58,12 @@ export const OrganizationProfileModal = (props: OrganizationProfileModalProps):
return (
-
+
{/*TODO: Used by InvisibleRootBox, can we simplify? */}
-
+
);
};
diff --git a/packages/clerk-js/src/ui/components/UserVerification/index.tsx b/packages/clerk-js/src/ui/components/UserVerification/index.tsx
index 6a5d1d38634..964487cdef7 100644
--- a/packages/clerk-js/src/ui/components/UserVerification/index.tsx
+++ b/packages/clerk-js/src/ui/components/UserVerification/index.tsx
@@ -1,7 +1,7 @@
import type { __experimental_UserVerificationModalProps, __experimental_UserVerificationProps } from '@clerk/types';
import React, { useEffect } from 'react';
-import { ComponentContext, withCoreSessionSwitchGuard } from '../../contexts';
+import { UserVerificationContext, withCoreSessionSwitchGuard } from '../../contexts';
import { Flow } from '../../customizables';
import { Route, Switch } from '../../router';
import { UserVerificationFactorOne } from './UserVerificationFactorOne';
@@ -37,7 +37,7 @@ const UserVerification: React.ComponentType<__experimental_UserVerificationProps
const UserVerificationModal = (props: __experimental_UserVerificationModalProps): JSX.Element => {
return (
-
-
+
);
};
diff --git a/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx b/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx
index b88a8f7e5d3..398b4707e02 100644
--- a/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx
+++ b/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx
@@ -1,7 +1,7 @@
import { useClerk } from '@clerk/shared/react';
import type { WaitlistModalProps } from '@clerk/types';
-import { ComponentContext, useWaitlistContext } from '../../contexts';
+import { useWaitlistContext, WaitlistContext } from '../../contexts';
import { Flow, localizationKeys } from '../../customizables';
import { Card, withCardStateProvider } from '../../elements';
import { Route, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
@@ -52,11 +52,11 @@ export const WaitlistModal = (props: WaitlistModalProps): JSX.Element => {
return (
-
+
-
+
);
};
diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
index 1ce81c775b5..429887243da 100644
--- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
+++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
@@ -1,7 +1,13 @@
import { deprecatedObjectProperty } from '@clerk/shared/deprecated';
import { useClerk } from '@clerk/shared/react';
import { snakeToCamel } from '@clerk/shared/underscore';
-import type { HandleOAuthCallbackParams, OrganizationResource, UserResource } from '@clerk/types';
+import type {
+ HandleOAuthCallbackParams,
+ OrganizationResource,
+ UserButtonProps,
+ UserResource,
+ WaitlistProps,
+} from '@clerk/types';
import React, { useCallback, useMemo } from 'react';
import { SIGN_IN_INITIAL_VALUE_KEYS, SIGN_UP_INITIAL_VALUE_KEYS } from '../../core/constants';
@@ -13,7 +19,8 @@ import type { NavbarRoute } from '../elements';
import type { ParsedQueryString } from '../router';
import { useRouter } from '../router';
import type {
- AvailableComponentCtx,
+ AvailableComponentName,
+ AvailableComponentProps,
CreateOrganizationCtx,
GoogleOneTapCtx,
OrganizationListCtx,
@@ -35,26 +42,70 @@ import {
const populateParamFromObject = createDynamicParamParser({ regex: /:(\w+)/ });
-export const ComponentContext = React.createContext(null);
-
-export function componentContextWrapper({ componentName }: { componentName: 'SignIn' }): typeof SignInContext;
-export function componentContextWrapper({ componentName }: { componentName: 'SignUp' }): typeof SignUpContext;
-export function componentContextWrapper({ componentName }: { componentName: 'UserProfile' }): typeof UserProfileContext;
-export function componentContextWrapper({ componentName }: { componentName: string }): typeof ComponentContext;
-export function componentContextWrapper({
+export function ComponentContextProvider({
componentName,
+ props,
+ children,
}: {
- componentName: string;
-}): typeof SignInContext | typeof SignUpContext | typeof UserProfileContext | typeof ComponentContext {
+ componentName: AvailableComponentName;
+ props: AvailableComponentProps;
+ children: React.ReactNode;
+}) {
switch (componentName) {
case 'SignIn':
- return SignInContext;
+ return {children};
case 'SignUp':
- return SignUpContext;
+ return {children};
case 'UserProfile':
- return UserProfileContext;
+ return {children};
+ case 'UserVerification':
+ return (
+
+ {children}
+
+ );
+ case 'UserButton':
+ return (
+
+ {children}
+
+ );
+ case 'OrganizationSwitcher':
+ return (
+
+ {children}
+
+ );
+ case 'OrganizationList':
+ return (
+
+ {children}
+
+ );
+ case 'OrganizationProfile':
+ return (
+
+ {children}
+
+ );
+ case 'CreateOrganization':
+ return (
+
+ {children}
+
+ );
+ case 'GoogleOneTap':
+ return (
+ {children}
+ );
+ case 'Waitlist':
+ return (
+
+ {children}
+
+ );
default:
- return ComponentContext;
+ throw new Error(`Unknown component context: ${componentName}`);
}
}
@@ -291,30 +342,38 @@ export const useUserProfileContext = (): UserProfileContextType => {
export type UserVerificationContextType = UserVerificationCtx;
+export const UserVerificationContext = React.createContext(null);
+
export const useUserVerification = (): UserVerificationContextType => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as UserVerificationCtx;
+ const context = React.useContext(UserVerificationContext);
- if (componentName !== 'UserVerification') {
+ if (!context || context.componentName !== 'UserVerification') {
throw new Error('Clerk: useUserVerificationContext called outside of the mounted UserVerification component.');
}
+ const { componentName, ...ctx } = context;
+
return {
...ctx,
componentName,
};
};
+export const UserButtonContext = React.createContext(null);
+
export const useUserButtonContext = () => {
- const { componentName, customMenuItems, ...ctx } = (React.useContext(ComponentContext) || {}) as UserButtonCtx;
+ const context = React.useContext(UserButtonContext);
const clerk = useClerk();
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
const options = useOptions();
- if (componentName !== 'UserButton') {
+ if (!context || context.componentName !== 'UserButton') {
throw new Error('Clerk: useUserButtonContext called outside of the mounted UserButton component.');
}
+ const { componentName, customMenuItems, ...ctx } = context;
+
const signInUrl = ctx.signInUrl || options.signInUrl || displayConfig.signInUrl;
const userProfileUrl = ctx.userProfileUrl || displayConfig.userProfileUrl;
@@ -361,15 +420,19 @@ export const useUserButtonContext = () => {
};
};
+export const OrganizationSwitcherContext = React.createContext(null);
+
export const useOrganizationSwitcherContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as OrganizationSwitcherCtx;
+ const context = React.useContext(OrganizationSwitcherContext);
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
- if (componentName !== 'OrganizationSwitcher') {
+ if (!context || context.componentName !== 'OrganizationSwitcher') {
throw new Error('Clerk: useOrganizationSwitcherContext called outside OrganizationSwitcher.');
}
+ const { componentName, ...ctx } = context;
+
const afterCreateOrganizationUrl = ctx.afterCreateOrganizationUrl || displayConfig.afterCreateOrganizationUrl;
const afterLeaveOrganizationUrl = ctx.afterLeaveOrganizationUrl || displayConfig.afterLeaveOrganizationUrl;
@@ -464,15 +527,19 @@ export const useOrganizationSwitcherContext = () => {
};
};
+export const OrganizationListContext = React.createContext(null);
+
export const useOrganizationListContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as unknown as OrganizationListCtx;
+ const context = React.useContext(OrganizationListContext);
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
- if (componentName !== 'OrganizationList') {
+ if (!context || context.componentName !== 'OrganizationList') {
throw new Error('Clerk: useOrganizationListContext called outside OrganizationList.');
}
+ const { componentName, ...ctx } = context;
+
const afterCreateOrganizationUrl = ctx.afterCreateOrganizationUrl || displayConfig.afterCreateOrganizationUrl;
const navigateAfterCreateOrganization = (organization: OrganizationResource) => {
@@ -550,16 +617,20 @@ export type OrganizationProfileContextType = OrganizationProfileCtx & {
isGeneralPageRoot: boolean;
};
+export const OrganizationProfileContext = React.createContext(null);
+
export const useOrganizationProfileContext = (): OrganizationProfileContextType => {
- const { componentName, customPages, ...ctx } = (React.useContext(ComponentContext) || {}) as OrganizationProfileCtx;
+ const context = React.useContext(OrganizationProfileContext);
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
const clerk = useClerk();
- if (componentName !== 'OrganizationProfile') {
+ if (!context || context.componentName !== 'OrganizationProfile') {
throw new Error('Clerk: useOrganizationProfileContext called outside OrganizationProfile.');
}
+ const { componentName, customPages, ...ctx } = context;
+
const pages = useMemo(() => createOrganizationProfileCustomPages(customPages || [], clerk), [customPages]);
const navigateAfterLeaveOrganization = () =>
@@ -581,15 +652,19 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType
};
};
+export const CreateOrganizationContext = React.createContext(null);
+
export const useCreateOrganizationContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as CreateOrganizationCtx;
+ const context = React.useContext(CreateOrganizationContext);
const { navigate } = useRouter();
const { displayConfig } = useEnvironment();
- if (componentName !== 'CreateOrganization') {
+ if (!context || context.componentName !== 'CreateOrganization') {
throw new Error('Clerk: useCreateOrganizationContext called outside CreateOrganization.');
}
+ const { componentName, ...ctx } = context;
+
const navigateAfterCreateOrganization = (organization: OrganizationResource) => {
if (typeof ctx.afterCreateOrganizationUrl === 'function') {
return navigate(ctx.afterCreateOrganizationUrl(organization));
@@ -615,16 +690,20 @@ export const useCreateOrganizationContext = () => {
};
};
+export const GoogleOneTapContext = React.createContext(null);
+
export const useGoogleOneTapContext = () => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as GoogleOneTapCtx;
+ const context = React.useContext(GoogleOneTapContext);
const options = useOptions();
const { displayConfig } = useEnvironment();
const { queryParams } = useRouter();
- if (componentName !== 'GoogleOneTap') {
+ if (!context || context.componentName !== 'GoogleOneTap') {
throw new Error('Clerk: useGoogleOneTapContext called outside GoogleOneTap.');
}
+ const { componentName, ...ctx } = context;
+
const generateCallbackUrls = useCallback(
(returnBackUrl: string): HandleOAuthCallbackParams => {
const redirectUrls = new RedirectUrls(
@@ -704,11 +783,19 @@ export type WaitlistContextType = WaitlistCtx & {
redirectUrl?: string;
};
+export const WaitlistContext = React.createContext(null);
+
export const useWaitlistContext = (): WaitlistContextType => {
- const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as WaitlistCtx;
+ const context = React.useContext(WaitlistContext);
const { displayConfig } = useEnvironment();
const options = useOptions();
+ if (!context || context.componentName !== 'Waitlist') {
+ throw new Error('Clerk: useWaitlistContext called outside Waitlist.');
+ }
+
+ const { componentName, ...ctx } = context;
+
let signInUrl = ctx.signInUrl || options.signInUrl || displayConfig.signInUrl;
signInUrl = buildURL({ base: signInUrl }, { stringify: true });
diff --git a/packages/clerk-js/src/ui/portal/index.tsx b/packages/clerk-js/src/ui/portal/index.tsx
index 862ce3ce261..69850b4a69d 100644
--- a/packages/clerk-js/src/ui/portal/index.tsx
+++ b/packages/clerk-js/src/ui/portal/index.tsx
@@ -5,16 +5,16 @@ import ReactDOM from 'react-dom';
import { PRESERVED_QUERYSTRING_PARAMS } from '../../core/constants';
import { clerkErrorPathRouterMissingPath } from '../../core/errors';
import { normalizeRoutingOptions } from '../../utils/normalizeRoutingOptions';
-import { ComponentContext, componentContextWrapper } from '../contexts';
+import { ComponentContextProvider } from '../contexts';
import { HashRouter, PathRouter, VirtualRouter } from '../router';
-import type { AvailableComponentCtx } from '../types';
+import type { AvailableComponentCtx, AvailableComponentName } from '../types';
type PortalProps> = {
node: HTMLDivElement;
component: React.FunctionComponent | React.ComponentClass;
// Aligning this with props attributes of ComponentControls
props?: PropsType & RoutingOptions;
-} & Pick;
+} & { componentName: AvailableComponentName };
export function Portal({
props,
@@ -24,13 +24,15 @@ export function Portal({
}: PortalProps) {
const normalizedProps = { ...props, ...normalizeRoutingOptions({ routing: props?.routing, path: props?.path }) };
- const ComponentContextProvider = componentContextWrapper({ componentName });
const el = (
-
+
{React.createElement(component, normalizedProps as PortalProps['props'])}
-
+
);
if (normalizedProps?.routing === 'path') {
@@ -56,7 +58,7 @@ type VirtualBodyRootPortalProps | React.ComponentClass;
props?: PropsType;
startPath: string;
-} & Pick;
+} & { componentName: AvailableComponentName };
export class VirtualBodyRootPortal extends React.PureComponent<
VirtualBodyRootPortalProps
@@ -76,9 +78,12 @@ export class VirtualBodyRootPortal extend
return ReactDOM.createPortal(
-
+
{React.createElement(component, props as PortalProps['props'])}
-
+
,
this.elRef,
);
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index 04564ce52b3..366358c2d7d 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -106,3 +106,5 @@ export type AvailableComponentCtx =
| OrganizationListCtx
| GoogleOneTapCtx
| WaitlistCtx;
+
+export type AvailableComponentName = AvailableComponentCtx['componentName'];
diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
index 303900c2a9d..1692abeb502 100644
--- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
+++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
@@ -5,22 +5,21 @@ import React from 'react';
import { Clerk as ClerkCtor } from '../../../core/clerk';
import { Client, Environment } from '../../../core/resources';
import {
- ComponentContext,
- componentContextWrapper,
+ ComponentContextProvider,
CoreClerkContextWrapper,
EnvironmentProvider,
+ GoogleOneTapContext,
OptionsProvider,
} from '../../contexts';
import { AppearanceProvider } from '../../customizables';
import { FlowMetadataProvider } from '../../elements';
import { RouteContext } from '../../router';
import { InternalThemeProvider } from '../../styledSystem';
+import type { AvailableComponentName, AvailableComponentProps } from '../../types';
import { createClientFixtureHelpers, createEnvironmentFixtureHelpers } from './fixtureHelpers';
import { createBaseClientJSON, createBaseEnvironmentJSON } from './fixtures';
import { mockClerkMethods, mockRouteContextValue } from './mockHelpers';
-type UnpackContext = NonNullable ? U : T>;
-
const createInitialStateConfigParam = (baseEnvironment: EnvironmentJSON, baseClient: ClientJSON) => {
return {
...createEnvironmentFixtureHelpers(baseEnvironment),
@@ -40,8 +39,8 @@ export const bindCreateFixtures = (
return { createFixtures: unboundCreateFixtures(componentName, mockOpts) };
};
-const unboundCreateFixtures = ['componentName']>(
- componentName: N,
+const unboundCreateFixtures = (
+ componentName: AvailableComponentName,
mockOpts?: {
router?: Parameters[0];
},
@@ -81,7 +80,7 @@ const unboundCreateFixtures = [
options: optionsMock,
};
- let componentContextProps: Partial & { componentName: N }>;
+ let componentContextProps: AvailableComponentProps;
const props = {
setProps: (props: typeof componentContextProps) => {
componentContextProps = props;
@@ -91,24 +90,31 @@ const unboundCreateFixtures = [
const MockClerkProvider = (props: any) => {
const { children } = props;
- const ContextProvider = componentContextWrapper({ componentName });
+ const componentsWithoutContext = ['UsernameSection', 'UserProfileSection'];
+ const contextWrappedChildren = !componentsWithoutContext.includes(componentName) ? (
+
+ {children}
+
+ ) : (
+ <>{children}>
+ );
+
return (
new Map() }}
>
-
+
-
-
- {children}
-
-
+ {contextWrappedChildren}
From b4225f178a818bec40fd2ba6bef79b460ebcbfe0 Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Wed, 6 Nov 2024 10:36:17 -0800
Subject: [PATCH 5/5] fix(clerk-js): Remove unnecessary GoogleOneTapProvider
---
packages/clerk-js/src/ui/utils/test/createFixtures.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
index 1692abeb502..6ce3783e8dd 100644
--- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
+++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
@@ -8,7 +8,6 @@ import {
ComponentContextProvider,
CoreClerkContextWrapper,
EnvironmentProvider,
- GoogleOneTapContext,
OptionsProvider,
} from '../../contexts';
import { AppearanceProvider } from '../../customizables';
@@ -108,7 +107,6 @@ const unboundCreateFixtures = (
// Clear swr cache
swrConfig={{ provider: () => new Map() }}
>
-