From abe85ee4ad49c0e00658b2edf6ff0f3bcafc349d Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 22 Jan 2026 10:39:28 -0600 Subject: [PATCH 1/5] feat(core): Split setActive into selectSession and selectOrganization Add two new purpose-specific methods to replace the generic setActive: - selectSession: For session selection (sign-in, sign-up, session switching) - selectOrganization: For organization selection (org switching, personal workspace) Changes: - Add SelectSessionOptions, SelectOrganizationOptions types - Add selectSession and selectOrganization methods to Clerk core - Update React hooks (useSignIn, useSignUp) to return selectSession - Update shared hooks (useSessionList, useOrganizationList) with new methods - Update framework integrations (Next.js, Vue, Expo) - Rename internal hooks: __internal_onBeforeSelectSession, __internal_onAfterSelectSession setActive is currently kept as deprecated for backwards compatibility. --- packages/clerk-js/src/core/clerk.ts | 97 +++++++++++++-- packages/clerk-js/src/global.d.ts | 4 +- packages/expo/src/hooks/useOAuth.ts | 9 +- packages/expo/src/hooks/useSSO.ts | 8 +- .../src/app-router/client/ClerkProvider.tsx | 8 +- packages/nextjs/src/global.d.ts | 4 +- packages/nextjs/src/pages/ClerkProvider.tsx | 4 +- packages/react/src/hooks/legacy/useSignIn.ts | 3 +- packages/react/src/hooks/legacy/useSignUp.ts | 3 +- packages/react/src/isomorphicClerk.ts | 33 ++++- .../src/react/hooks/useOrganizationList.tsx | 17 ++- .../shared/src/react/hooks/useSessionList.ts | 3 +- packages/shared/src/types/clerk.ts | 117 +++++++++++++++++- packages/shared/src/types/hooks.ts | 40 +++++- .../vue/src/composables/useSessionList.ts | 4 +- packages/vue/src/composables/useSignIn.ts | 3 +- packages/vue/src/composables/useSignUp.ts | 3 +- 17 files changed, 320 insertions(+), 40 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 38a8e411478..82167d15734 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -97,6 +97,8 @@ import type { RedirectOptions, Resources, SDKMetadata, + SelectOrganizationOptions, + SelectSessionOptions, SessionResource, SetActiveParams, SignedInSessionResource, @@ -297,7 +299,7 @@ export class Clerk implements ClerkInterface { public __internal_isWebAuthnAutofillSupported: (() => Promise) | undefined; public __internal_isWebAuthnPlatformAuthenticatorSupported: (() => Promise) | undefined; - public __internal_setActiveInProgress = false; + public __internal_selectSessionInProgress = false; get publishableKey(): string { return this.#publishableKey; @@ -583,13 +585,13 @@ export class Clerk implements ClerkInterface { } const onBeforeSetActive: SetActiveHook = - typeof window !== 'undefined' && typeof window.__internal_onBeforeSetActive === 'function' - ? window.__internal_onBeforeSetActive + typeof window !== 'undefined' && typeof window.__internal_onBeforeSelectSession === 'function' + ? window.__internal_onBeforeSelectSession : noop; const onAfterSetActive: SetActiveHook = - typeof window !== 'undefined' && typeof window.__internal_onAfterSetActive === 'function' - ? window.__internal_onAfterSetActive + typeof window !== 'undefined' && typeof window.__internal_onAfterSelectSession === 'function' + ? window.__internal_onAfterSelectSession : noop; const opts = callbackOrOptions && typeof callbackOrOptions === 'object' ? callbackOrOptions : options || {}; @@ -1446,7 +1448,7 @@ export class Clerk implements ClerkInterface { public setActive = async (params: SetActiveParams): Promise => { const { organization, redirectUrl, navigate: setActiveNavigate } = params; let { session } = params; - this.__internal_setActiveInProgress = true; + this.__internal_selectSessionInProgress = true; debugLogger.debug( 'setActive() start', { @@ -1476,13 +1478,13 @@ export class Clerk implements ClerkInterface { } const onBeforeSetActive: SetActiveHook = - typeof window !== 'undefined' && typeof window.__internal_onBeforeSetActive === 'function' - ? window.__internal_onBeforeSetActive + typeof window !== 'undefined' && typeof window.__internal_onBeforeSelectSession === 'function' + ? window.__internal_onBeforeSelectSession : noop; const onAfterSetActive: SetActiveHook = - typeof window !== 'undefined' && typeof window.__internal_onAfterSetActive === 'function' - ? window.__internal_onAfterSetActive + typeof window !== 'undefined' && typeof window.__internal_onAfterSelectSession === 'function' + ? window.__internal_onAfterSelectSession : noop; let newSession = session === undefined ? this.session : session; @@ -1605,10 +1607,79 @@ export class Clerk implements ClerkInterface { await onAfterSetActive(); } } finally { - this.__internal_setActiveInProgress = false; + this.__internal_selectSessionInProgress = false; } }; + /** + * Select a session to make active. + * + * Use this method after sign-in or sign-up to activate the created session, + * or to switch between sessions in a multi-session application. + * + * Pass `null` to sign out and clear the active session. + */ + public selectSession = async ( + session: SignedInSessionResource | string | null, + options?: SelectSessionOptions, + ): Promise => { + const { navigate, redirectUrl } = options ?? {}; + + // Convert navigate callback to setActive format if provided + const setActiveNavigate = navigate + ? async ({ session: s }: { session: SessionResource }) => { + const result = await navigate({ session: s }); + if (typeof result === 'string') { + await this.navigate(result); + } + } + : undefined; + + return this.setActive({ + session, + navigate: setActiveNavigate, + redirectUrl, + }); + }; + + /** + * Select an organization to make active within the current session. + * + * Use this method to switch between organizations or to select a personal workspace. + * + * Pass `null` to switch to the personal workspace (no active organization). + */ + public selectOrganization = async ( + organization: OrganizationResource | string | null, + options?: SelectOrganizationOptions, + ): Promise => { + const { navigate, redirectUrl } = options ?? {}; + + // Convert navigate callback to setActive format if provided + const setActiveNavigate = navigate + ? async ({ session }: { session: SessionResource }) => { + // Resolve the organization for the callback + const org = + organization === null + ? null + : typeof organization === 'string' + ? (this.organization ?? null) + : organization; + + const result = await navigate({ session, organization: org }); + if (typeof result === 'string') { + await this.navigate(result); + } + } + : undefined; + + return this.setActive({ + organization, + navigate: setActiveNavigate, + redirectUrl, + }); + }; + public addListener = (listener: ListenerCallback): UnsubscribeCallback => { listener = memoizeListenerCallback(listener); this.#listeners.push(listener); @@ -2596,8 +2667,8 @@ export class Clerk implements ClerkInterface { const hasTransitionedToPendingStatus = this.session.status === 'active' && session?.status === 'pending'; if (hasTransitionedToPendingStatus) { const onAfterSetActive: SetActiveHook = - typeof window !== 'undefined' && typeof window.__internal_onAfterSetActive === 'function' - ? window.__internal_onAfterSetActive + typeof window !== 'undefined' && typeof window.__internal_onAfterSelectSession === 'function' + ? window.__internal_onAfterSelectSession : noop; // Execute hooks to update server authentication context and trigger diff --git a/packages/clerk-js/src/global.d.ts b/packages/clerk-js/src/global.d.ts index 52a15e8dee6..ae7014334c1 100644 --- a/packages/clerk-js/src/global.d.ts +++ b/packages/clerk-js/src/global.d.ts @@ -15,8 +15,8 @@ const __BUILD_VARIANT_CHANNEL__: boolean; const __BUILD_VARIANT_CHIPS__: boolean; interface Window { - __internal_onBeforeSetActive: (intent?: 'sign-out') => Promise | void; - __internal_onAfterSetActive: () => Promise | void; + __internal_onBeforeSelectSession: (intent?: 'sign-out') => Promise | void; + __internal_onAfterSelectSession: () => Promise | void; // eslint-disable-next-line @typescript-eslint/consistent-type-imports __internal_ClerkUiCtor?: import('@clerk/shared/types').ClerkUiConstructor; /** diff --git a/packages/expo/src/hooks/useOAuth.ts b/packages/expo/src/hooks/useOAuth.ts index 9779e2d1d11..eff2319b5e8 100644 --- a/packages/expo/src/hooks/useOAuth.ts +++ b/packages/expo/src/hooks/useOAuth.ts @@ -1,5 +1,5 @@ import { useSignIn, useSignUp } from '@clerk/react/legacy'; -import type { OAuthStrategy, SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; +import type { OAuthStrategy, SelectSessionHook, SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; import * as AuthSession from 'expo-auth-session'; import * as WebBrowser from 'expo-web-browser'; @@ -18,6 +18,8 @@ export type StartOAuthFlowParams = { export type StartOAuthFlowReturnType = { createdSessionId: string; + selectSession?: SelectSessionHook; + /** @deprecated Use `selectSession` instead. */ setActive?: SetActive; signIn?: SignInResource; signUp?: SignUpResource; @@ -33,7 +35,7 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { return errorThrower.throw('Missing oauth strategy'); } - const { signIn, setActive, isLoaded: isSignInLoaded } = useSignIn(); + const { signIn, selectSession, setActive, isLoaded: isSignInLoaded } = useSignIn(); const { signUp, isLoaded: isSignUpLoaded } = useSignUp(); async function startOAuthFlow(startOAuthFlowParams?: StartOAuthFlowParams): Promise { @@ -42,6 +44,7 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { createdSessionId: '', signIn, signUp, + selectSession, setActive, }; } @@ -79,6 +82,7 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { return { authSessionResult, createdSessionId: '', + selectSession, setActive, signIn, signUp, @@ -108,6 +112,7 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { return { authSessionResult, createdSessionId, + selectSession, setActive, signIn, signUp, diff --git a/packages/expo/src/hooks/useSSO.ts b/packages/expo/src/hooks/useSSO.ts index 797cbc85ff8..927e9bf13be 100644 --- a/packages/expo/src/hooks/useSSO.ts +++ b/packages/expo/src/hooks/useSSO.ts @@ -2,6 +2,7 @@ import { useSignIn, useSignUp } from '@clerk/react/legacy'; import type { EnterpriseSSOStrategy, OAuthStrategy, + SelectSessionHook, SetActive, SignInResource, SignUpResource, @@ -28,13 +29,15 @@ export type StartSSOFlowParams = { export type StartSSOFlowReturnType = { createdSessionId: string | null; authSessionResult: WebBrowser.WebBrowserAuthSessionResult | null; + selectSession?: SelectSessionHook; + /** @deprecated Use `selectSession` instead. */ setActive?: SetActive; signIn?: SignInResource; signUp?: SignUpResource; }; export function useSSO() { - const { signIn, setActive, isLoaded: isSignInLoaded } = useSignIn(); + const { signIn, selectSession, setActive, isLoaded: isSignInLoaded } = useSignIn(); const { signUp, isLoaded: isSignUpLoaded } = useSignUp(); async function startSSOFlow(startSSOFlowParams: StartSSOFlowParams): Promise { @@ -44,6 +47,7 @@ export function useSSO() { authSessionResult: null, signIn, signUp, + selectSession, setActive, }; } @@ -81,6 +85,7 @@ export function useSSO() { if (authSessionResult.type !== 'success' || !authSessionResult.url) { return { createdSessionId: null, + selectSession, setActive, signIn, signUp, @@ -102,6 +107,7 @@ export function useSSO() { return { createdSessionId: signUp.createdSessionId ?? signIn.createdSessionId, + selectSession, setActive, signIn, signUp, diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index ee9d6d6c6fe..356bef9f093 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -37,7 +37,7 @@ const NextClientClerkProvider = (props: NextClerkProviderPr } useSafeLayoutEffect(() => { - window.__internal_onBeforeSetActive = intent => { + window.__internal_onBeforeSelectSession = intent => { /** * We need to invalidate the cache in case the user is navigating to a page that * was previously cached using the auth state that was active at the time. @@ -61,12 +61,12 @@ const NextClientClerkProvider = (props: NextClerkProviderPr const nextVersion = window?.next?.version || ''; // On Next.js 15+ calling a server action that returns a 404 error when deployed on Vercel is prohibited, failing with 405 status code. - // When a user transitions from "signed in" to "signed out", we clear the `__session` cookie, then we call `__internal_onBeforeSetActive`. + // When a user transitions from "signed in" to "signed out", we clear the `__session` cookie, then we call `__internal_onBeforeSelectSession`. // If we were to call `invalidateCacheAction` while the user is already signed out (deleted cookie), any page protected by `auth.protect()` // will result to the server action returning a 404 error (this happens because server actions inherit the protection rules of the page they are called from). // SOLUTION: // To mitigate this, since the router cache on version 15+ is much less aggressive, we can treat this as a noop and simply resolve the promise. - // Once `setActive` performs the navigation, `__internal_onAfterSetActive` will kick in and perform a router.refresh ensuring shared layouts will also update with the correct authentication context. + // Once `setActive` performs the navigation, `__internal_onAfterSelectSession` will kick in and perform a router.refresh ensuring shared layouts will also update with the correct authentication context. if ((nextVersion.startsWith('15') || nextVersion.startsWith('16')) && intent === 'sign-out') { resolve(); // noop } else { @@ -75,7 +75,7 @@ const NextClientClerkProvider = (props: NextClerkProviderPr }); }; - window.__internal_onAfterSetActive = () => { + window.__internal_onAfterSelectSession = () => { if (__internal_invokeMiddlewareOnAuthStateChange) { return router.refresh(); } diff --git a/packages/nextjs/src/global.d.ts b/packages/nextjs/src/global.d.ts index efefffd1ed0..c6bec4b9013 100644 --- a/packages/nextjs/src/global.d.ts +++ b/packages/nextjs/src/global.d.ts @@ -38,8 +38,8 @@ interface Window { __clerk_nav_await: Array<(value: void) => void>; __clerk_nav: (to: string) => Promise; - __internal_onBeforeSetActive: (intent?: 'sign-out') => void | Promise; - __internal_onAfterSetActive: () => void | Promise; + __internal_onBeforeSelectSession: (intent?: 'sign-out') => void | Promise; + __internal_onAfterSelectSession: () => void | Promise; next?: { version: string; diff --git a/packages/nextjs/src/pages/ClerkProvider.tsx b/packages/nextjs/src/pages/ClerkProvider.tsx index 786da7c7654..bcb2fcaf6c3 100644 --- a/packages/nextjs/src/pages/ClerkProvider.tsx +++ b/packages/nextjs/src/pages/ClerkProvider.tsx @@ -23,11 +23,11 @@ export function ClerkProvider({ children, ...props }: NextC ReactClerkProvider.displayName = 'ReactClerkProvider'; useSafeLayoutEffect(() => { - window.__internal_onBeforeSetActive = invalidateNextRouterCache; + window.__internal_onBeforeSelectSession = invalidateNextRouterCache; }, []); useSafeLayoutEffect(() => { - window.__internal_onAfterSetActive = () => { + window.__internal_onAfterSelectSession = () => { // Re-run the middleware every time there auth state changes. // This enables complete control from a centralized place (NextJS middleware), // as we will invoke it every time the client-side auth state changes, eg: signing-out, switching orgs, etc.\ diff --git a/packages/react/src/hooks/legacy/useSignIn.ts b/packages/react/src/hooks/legacy/useSignIn.ts index 8068f2b184d..a1e8f9e50d8 100644 --- a/packages/react/src/hooks/legacy/useSignIn.ts +++ b/packages/react/src/hooks/legacy/useSignIn.ts @@ -58,12 +58,13 @@ export const useSignIn = (): UseSignInReturn => { isomorphicClerk.telemetry?.record(eventMethodCalled('useSignIn')); if (!client) { - return { isLoaded: false, signIn: undefined, setActive: undefined }; + return { isLoaded: false, signIn: undefined, selectSession: undefined, setActive: undefined }; } return { isLoaded: true, signIn: client.signIn, + selectSession: isomorphicClerk.selectSession, setActive: isomorphicClerk.setActive, }; }; diff --git a/packages/react/src/hooks/legacy/useSignUp.ts b/packages/react/src/hooks/legacy/useSignUp.ts index bee9c7fdf3d..0034316a531 100644 --- a/packages/react/src/hooks/legacy/useSignUp.ts +++ b/packages/react/src/hooks/legacy/useSignUp.ts @@ -58,12 +58,13 @@ export const useSignUp = (): UseSignUpReturn => { isomorphicClerk.telemetry?.record(eventMethodCalled('useSignUp')); if (!client) { - return { isLoaded: false, signUp: undefined, setActive: undefined }; + return { isLoaded: false, signUp: undefined, selectSession: undefined, setActive: undefined }; } return { isLoaded: true, signUp: client.signUp, + selectSession: isomorphicClerk.selectSession, setActive: isomorphicClerk.setActive, }; }; diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b39bf352b8c..279933b5d45 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -40,7 +40,10 @@ import type { OrganizationSwitcherProps, PricingTableProps, RedirectOptions, + SelectOrganizationOptions, + SelectSessionOptions, SetActiveParams, + SignedInSessionResource, SignInProps, SignInRedirectOptions, SignInResource, @@ -115,7 +118,7 @@ type IsomorphicLoadedClerk = Without< | '__internal_reloadInitialResources' | 'billing' | 'apiKeys' - | '__internal_setActiveInProgress' + | '__internal_selectSessionInProgress' > & { client: ClientResource | undefined; billing: BillingNamespace | undefined; @@ -814,6 +817,34 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; + /** + * Select a session to make active. + */ + selectSession = ( + session: SignedInSessionResource | string | null, + options?: SelectSessionOptions, + ): Promise => { + if (this.clerkjs) { + return this.clerkjs.selectSession(session, options); + } else { + return Promise.reject(); + } + }; + + /** + * Select an organization to make active. + */ + selectOrganization = ( + organization: OrganizationResource | string | null, + options?: SelectOrganizationOptions, + ): Promise => { + if (this.clerkjs) { + return this.clerkjs.selectOrganization(organization, options); + } else { + return Promise.reject(); + } + }; + openSignIn = (props?: SignInProps) => { if (this.clerkjs && this.loaded) { this.clerkjs.openSignIn(props); diff --git a/packages/shared/src/react/hooks/useOrganizationList.tsx b/packages/shared/src/react/hooks/useOrganizationList.tsx index 8060e48a22f..8d9eeed4f4a 100644 --- a/packages/shared/src/react/hooks/useOrganizationList.tsx +++ b/packages/shared/src/react/hooks/useOrganizationList.tsx @@ -7,6 +7,7 @@ import type { OrganizationMembershipResource, OrganizationResource, OrganizationSuggestionResource, + SelectOrganizationOptions, SetActive, UserOrganizationInvitationResource, } from '../../types'; @@ -83,8 +84,13 @@ export type UseOrganizationListReturn = * A function that returns a `Promise` which resolves to the newly created `Organization`. */ createOrganization: undefined; + /** + * A function that selects an organization to make active within the current session. + */ + selectOrganization: undefined; /** * A function that sets the active session and/or Organization. + * @deprecated Use `selectOrganization` instead. */ setActive: undefined; /** @@ -103,6 +109,13 @@ export type UseOrganizationListReturn = | { isLoaded: boolean; createOrganization: (CreateOrganizationParams: CreateOrganizationParams) => Promise; + selectOrganization: ( + organization: OrganizationResource | string | null, + options?: SelectOrganizationOptions, + ) => Promise; + /** + * @deprecated Use `selectOrganization` instead. + */ setActive: SetActive; userMemberships: PaginatedResources< OrganizationMembershipResource, @@ -382,6 +395,7 @@ export function useOrganizationList(params? return { isLoaded: false, createOrganization: undefined, + selectOrganization: undefined, setActive: undefined, userMemberships: undefinedPaginatedResource, userInvitations: undefinedPaginatedResource, @@ -391,8 +405,9 @@ export function useOrganizationList(params? return { isLoaded: isClerkLoaded, - setActive: clerk.setActive, createOrganization: clerk.createOrganization, + selectOrganization: clerk.selectOrganization, + setActive: clerk.setActive, userMemberships: memberships, userInvitations: invitations, userSuggestions: suggestions, diff --git a/packages/shared/src/react/hooks/useSessionList.ts b/packages/shared/src/react/hooks/useSessionList.ts index f92cda3f31a..ad53ce64c2b 100644 --- a/packages/shared/src/react/hooks/useSessionList.ts +++ b/packages/shared/src/react/hooks/useSessionList.ts @@ -56,12 +56,13 @@ export const useSessionList = (): UseSessionListReturn => { clerk.telemetry?.record(eventMethodCalled(hookName)); if (!client) { - return { isLoaded: false, sessions: undefined, setActive: undefined }; + return { isLoaded: false, sessions: undefined, selectSession: undefined, setActive: undefined }; } return { isLoaded: true, sessions: client.sessions, + selectSession: isomorphicClerk.selectSession, setActive: isomorphicClerk.setActive, }; }; diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 314d186793b..2d02c68fd30 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -136,6 +136,57 @@ export type SDKMetadata = { export type ListenerCallback = (emission: Resources) => void; export type UnsubscribeCallback = () => void; + +/** + * Navigate callback for selectSession. + * Called after session selection with the new session. + */ +export type SelectSessionNavigate = (params: { + session: SessionResource | null; +}) => string | Promise | void; + +/** + * Navigate callback for selectOrganization. + * Called after organization selection with both session and organization. + */ +export type SelectOrganizationNavigate = (params: { + session: SessionResource | null; + organization: OrganizationResource | null; +}) => string | Promise | void; + +/** + * Options for selectSession method. + */ +export interface SelectSessionOptions { + /** + * A custom navigation function called after session selection. + * When provided, takes precedence over redirectUrl. + */ + navigate?: SelectSessionNavigate; + /** + * URL to redirect to after session selection. + */ + redirectUrl?: string; +} + +/** + * Options for selectOrganization method. + */ +export interface SelectOrganizationOptions { + /** + * A custom navigation function called after organization selection. + * When provided, takes precedence over redirectUrl. + */ + navigate?: SelectOrganizationNavigate; + /** + * URL to redirect to after organization selection. + */ + redirectUrl?: string; +} + +/** + * @deprecated Use SelectSessionNavigate instead + */ export type SetActiveNavigate = ({ session }: { session: SessionResource }) => void | Promise; export type SignOutCallback = () => void | Promise; @@ -723,9 +774,70 @@ export interface Clerk { * * If the session param is `null`, the active session is deleted. * In a similar fashion, if the organization param is `null`, the current organization is removed as active. + * + * @deprecated Use `selectSession()` or `selectOrganization()` instead. */ setActive: SetActive; + /** + * Select a session to make active. + * + * Use this method after sign-in or sign-up to activate the created session, + * or to switch between sessions in a multi-session application. + * + * Pass `null` to sign out and clear the active session. + * + * @param session - The session to select, or null to sign out + * @param options - Optional configuration for navigation after selection + * + * @example + * ```typescript + * // After sign-in, activate the new session + * await clerk.selectSession(signIn.createdSessionId); + * + * // With custom navigation + * await clerk.selectSession(sessionId, { + * navigate: ({ session }) => `/dashboard/${session?.user.id}` + * }); + * + * // Sign out + * await clerk.selectSession(null, { redirectUrl: '/goodbye' }); + * ``` + */ + selectSession: ( + session: SignedInSessionResource | string | null, + options?: SelectSessionOptions, + ) => Promise; + + /** + * Select an organization to make active within the current session. + * + * Use this method to switch between organizations or to select a personal workspace. + * + * Pass `null` to switch to the personal workspace (no active organization). + * + * @param organization - The organization to select, or null for personal workspace + * @param options - Optional configuration for navigation after selection + * + * @example + * ```typescript + * // Switch to an organization + * await clerk.selectOrganization(org.id); + * + * // With custom navigation + * await clerk.selectOrganization(org, { + * navigate: ({ organization }) => `/org/${organization?.slug}` + * }); + * + * // Switch to personal workspace + * await clerk.selectOrganization(null, { redirectUrl: '/personal' }); + * ``` + */ + selectOrganization: ( + organization: OrganizationResource | string | null, + options?: SelectOrganizationOptions, + ) => Promise; + /** * Function used to commit a navigation after certain steps in the Clerk processes. */ @@ -965,10 +1077,10 @@ export interface Clerk { __internal_reloadInitialResources: () => Promise; /** - * Internal flag indicating whether a `setActive` call is in progress. Used to prevent navigations from being + * Internal flag indicating whether a `selectSession` call is in progress. Used to prevent navigations from being * initiated outside of the Clerk class. */ - __internal_setActiveInProgress: boolean; + __internal_selectSessionInProgress: boolean; /** * API Keys Object @@ -1327,6 +1439,7 @@ export type SignUpRedirectOptions = RedirectOptions & /** * The parameters for the `setActive()` method. * + * @deprecated Use `selectSession()` or `selectOrganization()` instead. * @interface */ export type SetActiveParams = { diff --git a/packages/shared/src/types/hooks.ts b/packages/shared/src/types/hooks.ts index 622d588478b..f5fd7772c0b 100644 --- a/packages/shared/src/types/hooks.ts +++ b/packages/shared/src/types/hooks.ts @@ -1,4 +1,4 @@ -import type { SetActive, SignOut } from './clerk'; +import type { SelectSessionOptions, SetActive, SignOut } from './clerk'; import type { ActClaim, JwtPayload } from './jwtv2'; import type { OrganizationCustomRoleKey } from './organizationMembership'; import type { @@ -117,6 +117,14 @@ export type UseAuthReturn = getToken: GetToken; }; +/** + * Type for the selectSession function returned by hooks. + */ +export type SelectSessionHook = ( + session: SignedInSessionResource | string | null, + options?: SelectSessionOptions, +) => Promise; + /** * @inline */ @@ -131,13 +139,21 @@ export type UseSignInReturn = */ signIn: undefined; /** - * A function that sets the active session. See the [reference doc](https://clerk.com/docs/reference/javascript/clerk#set-active). + * A function that selects a session to make active. Use after sign-in completes to activate the created session. + */ + selectSession: undefined; + /** + * @deprecated Use `selectSession` instead. */ setActive: undefined; } | { isLoaded: true; signIn: SignInResource; + selectSession: SelectSessionHook; + /** + * @deprecated Use `selectSession` instead. + */ setActive: SetActive; }; @@ -155,13 +171,21 @@ export type UseSignUpReturn = */ signUp: undefined; /** - * A function that sets the active session. See the [reference doc](https://clerk.com/docs/reference/javascript/clerk#set-active). + * A function that selects a session to make active. Use after sign-up completes to activate the created session. + */ + selectSession: undefined; + /** + * @deprecated Use `selectSession` instead. */ setActive: undefined; } | { isLoaded: true; signUp: SignUpResource; + selectSession: SelectSessionHook; + /** + * @deprecated Use `selectSession` instead. + */ setActive: SetActive; }; @@ -208,13 +232,21 @@ export type UseSessionListReturn = */ sessions: undefined; /** - * A function that sets the active session and/or Organization. See the [reference doc](https://clerk.com/docs/reference/javascript/clerk#set-active). + * A function that selects a session to make active from the available sessions. + */ + selectSession: undefined; + /** + * @deprecated Use `selectSession` instead. */ setActive: undefined; } | { isLoaded: true; sessions: SessionResource[]; + selectSession: SelectSessionHook; + /** + * @deprecated Use `selectSession` instead. + */ setActive: SetActive; }; diff --git a/packages/vue/src/composables/useSessionList.ts b/packages/vue/src/composables/useSessionList.ts index 86839f1d4bb..76a33280fa4 100644 --- a/packages/vue/src/composables/useSessionList.ts +++ b/packages/vue/src/composables/useSessionList.ts @@ -36,13 +36,15 @@ export const useSessionList: UseSessionList = () => { const result = computed(() => { if (!clientCtx.value) { - return { isLoaded: false, sessions: undefined, setActive: undefined }; + return { isLoaded: false, sessions: undefined, selectSession: undefined, setActive: undefined }; } return { isLoaded: true, sessions: clientCtx.value.sessions, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + selectSession: clerk.value!.selectSession, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion setActive: clerk.value!.setActive, }; }); diff --git a/packages/vue/src/composables/useSignIn.ts b/packages/vue/src/composables/useSignIn.ts index 4931829c592..b0f02745aae 100644 --- a/packages/vue/src/composables/useSignIn.ts +++ b/packages/vue/src/composables/useSignIn.ts @@ -41,12 +41,13 @@ export const useSignIn: UseSignIn = () => { const result = computed(() => { if (!clerk.value || !clientCtx.value) { - return { isLoaded: false, signIn: undefined, setActive: undefined }; + return { isLoaded: false, signIn: undefined, selectSession: undefined, setActive: undefined }; } return { isLoaded: true, signIn: clientCtx.value.signIn, + selectSession: clerk.value.selectSession, setActive: clerk.value.setActive, }; }); diff --git a/packages/vue/src/composables/useSignUp.ts b/packages/vue/src/composables/useSignUp.ts index 097a1cada60..bb17cc21d1a 100644 --- a/packages/vue/src/composables/useSignUp.ts +++ b/packages/vue/src/composables/useSignUp.ts @@ -41,12 +41,13 @@ export const useSignUp: UseSignUp = () => { const result = computed(() => { if (!clerk.value || !clientCtx.value) { - return { isLoaded: false, signUp: undefined, setActive: undefined }; + return { isLoaded: false, signUp: undefined, selectSession: undefined, setActive: undefined }; } return { isLoaded: true, signUp: clientCtx.value.signUp, + selectSession: clerk.value.selectSession, setActive: clerk.value.setActive, }; }); From d37f6f104b008d2895bdaee13305a87b98e208a4 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 22 Jan 2026 10:47:20 -0600 Subject: [PATCH 2/5] feat(core): Remove setActive from hook return types (clean break) - Remove setActive from UseSignInReturn, UseSignUpReturn, UseSessionListReturn, UseOrganizationListReturn types - Update React hooks (useSignIn, useSignUp) to return only selectSession - Update shared hooks (useSessionList, useOrganizationList) to return only selectSession/selectOrganization - Update Vue composables (useSignIn, useSignUp, useSessionList) to return only selectSession - Update Expo hooks (useSSO, useOAuth, useSignInWithApple) to return only selectSession - Update UI components to use selectOrganization from useOrganizationList - Rename useSetSessionWithTimeout to useSelectSessionWithTimeout --- packages/expo/src/hooks/useOAuth.ts | 9 ++----- packages/expo/src/hooks/useSSO.ts | 8 +----- .../expo/src/hooks/useSignInWithApple.ios.ts | 20 +++++++------- packages/react/src/hooks/legacy/useSignIn.ts | 3 +-- packages/react/src/hooks/legacy/useSignUp.ts | 3 +-- .../src/react/hooks/useOrganizationList.tsx | 12 --------- .../shared/src/react/hooks/useSessionList.ts | 3 +-- packages/shared/src/types/hooks.ts | 26 +------------------ .../CreateOrganizationForm.tsx | 4 +-- .../OrganizationList/UserMembershipList.tsx | 12 +++------ .../OrganizationSwitcherPopover.tsx | 7 +++-- .../ChooseOrganizationScreen.tsx | 5 ++-- .../CreateOrganizationScreen.tsx | 5 ++-- .../SignIn/ResetPasswordSuccess.tsx | 4 +-- .../ui/src/hooks/useSetSessionWithTimeout.ts | 13 +++++++--- .../vue/src/composables/useSessionList.ts | 4 +-- packages/vue/src/composables/useSignIn.ts | 3 +-- packages/vue/src/composables/useSignUp.ts | 3 +-- 18 files changed, 44 insertions(+), 100 deletions(-) diff --git a/packages/expo/src/hooks/useOAuth.ts b/packages/expo/src/hooks/useOAuth.ts index eff2319b5e8..0814502420a 100644 --- a/packages/expo/src/hooks/useOAuth.ts +++ b/packages/expo/src/hooks/useOAuth.ts @@ -1,5 +1,5 @@ import { useSignIn, useSignUp } from '@clerk/react/legacy'; -import type { OAuthStrategy, SelectSessionHook, SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; +import type { OAuthStrategy, SelectSessionHook, SignInResource, SignUpResource } from '@clerk/shared/types'; import * as AuthSession from 'expo-auth-session'; import * as WebBrowser from 'expo-web-browser'; @@ -19,8 +19,6 @@ export type StartOAuthFlowParams = { export type StartOAuthFlowReturnType = { createdSessionId: string; selectSession?: SelectSessionHook; - /** @deprecated Use `selectSession` instead. */ - setActive?: SetActive; signIn?: SignInResource; signUp?: SignUpResource; authSessionResult?: WebBrowser.WebBrowserAuthSessionResult; @@ -35,7 +33,7 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { return errorThrower.throw('Missing oauth strategy'); } - const { signIn, selectSession, setActive, isLoaded: isSignInLoaded } = useSignIn(); + const { signIn, selectSession, isLoaded: isSignInLoaded } = useSignIn(); const { signUp, isLoaded: isSignUpLoaded } = useSignUp(); async function startOAuthFlow(startOAuthFlowParams?: StartOAuthFlowParams): Promise { @@ -45,7 +43,6 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { signIn, signUp, selectSession, - setActive, }; } @@ -83,7 +80,6 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { authSessionResult, createdSessionId: '', selectSession, - setActive, signIn, signUp, }; @@ -113,7 +109,6 @@ export function useOAuth(useOAuthParams: UseOAuthFlowParams) { authSessionResult, createdSessionId, selectSession, - setActive, signIn, signUp, }; diff --git a/packages/expo/src/hooks/useSSO.ts b/packages/expo/src/hooks/useSSO.ts index 927e9bf13be..88c4584a1b9 100644 --- a/packages/expo/src/hooks/useSSO.ts +++ b/packages/expo/src/hooks/useSSO.ts @@ -3,7 +3,6 @@ import type { EnterpriseSSOStrategy, OAuthStrategy, SelectSessionHook, - SetActive, SignInResource, SignUpResource, } from '@clerk/shared/types'; @@ -30,14 +29,12 @@ export type StartSSOFlowReturnType = { createdSessionId: string | null; authSessionResult: WebBrowser.WebBrowserAuthSessionResult | null; selectSession?: SelectSessionHook; - /** @deprecated Use `selectSession` instead. */ - setActive?: SetActive; signIn?: SignInResource; signUp?: SignUpResource; }; export function useSSO() { - const { signIn, selectSession, setActive, isLoaded: isSignInLoaded } = useSignIn(); + const { signIn, selectSession, isLoaded: isSignInLoaded } = useSignIn(); const { signUp, isLoaded: isSignUpLoaded } = useSignUp(); async function startSSOFlow(startSSOFlowParams: StartSSOFlowParams): Promise { @@ -48,7 +45,6 @@ export function useSSO() { signIn, signUp, selectSession, - setActive, }; } @@ -86,7 +82,6 @@ export function useSSO() { return { createdSessionId: null, selectSession, - setActive, signIn, signUp, authSessionResult, @@ -108,7 +103,6 @@ export function useSSO() { return { createdSessionId: signUp.createdSessionId ?? signIn.createdSessionId, selectSession, - setActive, signIn, signUp, authSessionResult, diff --git a/packages/expo/src/hooks/useSignInWithApple.ios.ts b/packages/expo/src/hooks/useSignInWithApple.ios.ts index 23cb27ee25f..2ca54383e2a 100644 --- a/packages/expo/src/hooks/useSignInWithApple.ios.ts +++ b/packages/expo/src/hooks/useSignInWithApple.ios.ts @@ -1,5 +1,5 @@ import { useSignIn, useSignUp } from '@clerk/react/legacy'; -import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; +import type { SelectSessionHook, SignInResource, SignUpResource } from '@clerk/shared/types'; import { errorThrower } from '../utils/errors'; @@ -9,7 +9,7 @@ export type StartAppleAuthenticationFlowParams = { export type StartAppleAuthenticationFlowReturnType = { createdSessionId: string | null; - setActive?: SetActive; + selectSession?: SelectSessionHook; signIn?: SignInResource; signUp?: SignUpResource; }; @@ -32,10 +32,10 @@ export type StartAppleAuthenticationFlowReturnType = { * * const onPress = async () => { * try { - * const { createdSessionId, setActive } = await startAppleAuthenticationFlow(); + * const { createdSessionId, selectSession } = await startAppleAuthenticationFlow(); * - * if (createdSessionId && setActive) { - * await setActive({ session: createdSessionId }); + * if (createdSessionId && selectSession) { + * await selectSession(createdSessionId); * } * } catch (err) { * console.error('Apple Authentication error:', err); @@ -52,7 +52,7 @@ export type StartAppleAuthenticationFlowReturnType = { * @returns An object containing the `startAppleAuthenticationFlow` function */ export function useSignInWithApple() { - const { signIn, setActive, isLoaded: isSignInLoaded } = useSignIn(); + const { signIn, selectSession, isLoaded: isSignInLoaded } = useSignIn(); const { signUp, isLoaded: isSignUpLoaded } = useSignUp(); async function startAppleAuthenticationFlow( @@ -63,7 +63,7 @@ export function useSignInWithApple() { createdSessionId: null, signIn, signUp, - setActive, + selectSession, }; } @@ -124,7 +124,7 @@ export function useSignInWithApple() { return { createdSessionId: signUp.createdSessionId, - setActive, + selectSession, signIn, signUp, }; @@ -133,7 +133,7 @@ export function useSignInWithApple() { // User exists - return the SignIn session return { createdSessionId: signIn.createdSessionId, - setActive, + selectSession, signIn, signUp, }; @@ -143,7 +143,7 @@ export function useSignInWithApple() { // User canceled the sign-in flow return { createdSessionId: null, - setActive, + selectSession, signIn, signUp, }; diff --git a/packages/react/src/hooks/legacy/useSignIn.ts b/packages/react/src/hooks/legacy/useSignIn.ts index a1e8f9e50d8..6c256b1c4bd 100644 --- a/packages/react/src/hooks/legacy/useSignIn.ts +++ b/packages/react/src/hooks/legacy/useSignIn.ts @@ -58,13 +58,12 @@ export const useSignIn = (): UseSignInReturn => { isomorphicClerk.telemetry?.record(eventMethodCalled('useSignIn')); if (!client) { - return { isLoaded: false, signIn: undefined, selectSession: undefined, setActive: undefined }; + return { isLoaded: false, signIn: undefined, selectSession: undefined }; } return { isLoaded: true, signIn: client.signIn, selectSession: isomorphicClerk.selectSession, - setActive: isomorphicClerk.setActive, }; }; diff --git a/packages/react/src/hooks/legacy/useSignUp.ts b/packages/react/src/hooks/legacy/useSignUp.ts index 0034316a531..dd9b3463c8b 100644 --- a/packages/react/src/hooks/legacy/useSignUp.ts +++ b/packages/react/src/hooks/legacy/useSignUp.ts @@ -58,13 +58,12 @@ export const useSignUp = (): UseSignUpReturn => { isomorphicClerk.telemetry?.record(eventMethodCalled('useSignUp')); if (!client) { - return { isLoaded: false, signUp: undefined, selectSession: undefined, setActive: undefined }; + return { isLoaded: false, signUp: undefined, selectSession: undefined }; } return { isLoaded: true, signUp: client.signUp, selectSession: isomorphicClerk.selectSession, - setActive: isomorphicClerk.setActive, }; }; diff --git a/packages/shared/src/react/hooks/useOrganizationList.tsx b/packages/shared/src/react/hooks/useOrganizationList.tsx index 8d9eeed4f4a..423a4af04fc 100644 --- a/packages/shared/src/react/hooks/useOrganizationList.tsx +++ b/packages/shared/src/react/hooks/useOrganizationList.tsx @@ -8,7 +8,6 @@ import type { OrganizationResource, OrganizationSuggestionResource, SelectOrganizationOptions, - SetActive, UserOrganizationInvitationResource, } from '../../types'; import { useAssertWrappedByClerkProvider, useClerkInstanceContext, useUserContext } from '../contexts'; @@ -88,11 +87,6 @@ export type UseOrganizationListReturn = * A function that selects an organization to make active within the current session. */ selectOrganization: undefined; - /** - * A function that sets the active session and/or Organization. - * @deprecated Use `selectOrganization` instead. - */ - setActive: undefined; /** * Returns `PaginatedResources` which includes a list of the user's Organization memberships. */ @@ -113,10 +107,6 @@ export type UseOrganizationListReturn = organization: OrganizationResource | string | null, options?: SelectOrganizationOptions, ) => Promise; - /** - * @deprecated Use `selectOrganization` instead. - */ - setActive: SetActive; userMemberships: PaginatedResources< OrganizationMembershipResource, T['userMemberships'] extends { infinite: true } ? true : false @@ -396,7 +386,6 @@ export function useOrganizationList(params? isLoaded: false, createOrganization: undefined, selectOrganization: undefined, - setActive: undefined, userMemberships: undefinedPaginatedResource, userInvitations: undefinedPaginatedResource, userSuggestions: undefinedPaginatedResource, @@ -407,7 +396,6 @@ export function useOrganizationList(params? isLoaded: isClerkLoaded, createOrganization: clerk.createOrganization, selectOrganization: clerk.selectOrganization, - setActive: clerk.setActive, userMemberships: memberships, userInvitations: invitations, userSuggestions: suggestions, diff --git a/packages/shared/src/react/hooks/useSessionList.ts b/packages/shared/src/react/hooks/useSessionList.ts index ad53ce64c2b..49c566cfc3e 100644 --- a/packages/shared/src/react/hooks/useSessionList.ts +++ b/packages/shared/src/react/hooks/useSessionList.ts @@ -56,13 +56,12 @@ export const useSessionList = (): UseSessionListReturn => { clerk.telemetry?.record(eventMethodCalled(hookName)); if (!client) { - return { isLoaded: false, sessions: undefined, selectSession: undefined, setActive: undefined }; + return { isLoaded: false, sessions: undefined, selectSession: undefined }; } return { isLoaded: true, sessions: client.sessions, selectSession: isomorphicClerk.selectSession, - setActive: isomorphicClerk.setActive, }; }; diff --git a/packages/shared/src/types/hooks.ts b/packages/shared/src/types/hooks.ts index f5fd7772c0b..45e9fa7b0ec 100644 --- a/packages/shared/src/types/hooks.ts +++ b/packages/shared/src/types/hooks.ts @@ -1,4 +1,4 @@ -import type { SelectSessionOptions, SetActive, SignOut } from './clerk'; +import type { SelectSessionOptions, SignOut } from './clerk'; import type { ActClaim, JwtPayload } from './jwtv2'; import type { OrganizationCustomRoleKey } from './organizationMembership'; import type { @@ -142,19 +142,11 @@ export type UseSignInReturn = * A function that selects a session to make active. Use after sign-in completes to activate the created session. */ selectSession: undefined; - /** - * @deprecated Use `selectSession` instead. - */ - setActive: undefined; } | { isLoaded: true; signIn: SignInResource; selectSession: SelectSessionHook; - /** - * @deprecated Use `selectSession` instead. - */ - setActive: SetActive; }; /** @@ -174,19 +166,11 @@ export type UseSignUpReturn = * A function that selects a session to make active. Use after sign-up completes to activate the created session. */ selectSession: undefined; - /** - * @deprecated Use `selectSession` instead. - */ - setActive: undefined; } | { isLoaded: true; signUp: SignUpResource; selectSession: SelectSessionHook; - /** - * @deprecated Use `selectSession` instead. - */ - setActive: SetActive; }; /** @@ -235,19 +219,11 @@ export type UseSessionListReturn = * A function that selects a session to make active from the available sessions. */ selectSession: undefined; - /** - * @deprecated Use `selectSession` instead. - */ - setActive: undefined; } | { isLoaded: true; sessions: SessionResource[]; selectSession: SelectSessionHook; - /** - * @deprecated Use `selectSession` instead. - */ - setActive: SetActive; }; /** diff --git a/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx b/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx index 0bc7f29a13d..c4d655d18db 100644 --- a/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx +++ b/packages/ui/src/components/CreateOrganization/CreateOrganizationForm.tsx @@ -41,7 +41,7 @@ export const CreateOrganizationForm = withCardStateProvider((props: CreateOrgani const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); const lastCreatedOrganizationRef = React.useRef(null); - const { createOrganization, isLoaded, setActive, userMemberships } = useOrganizationList({ + const { createOrganization, isLoaded, selectOrganization, userMemberships } = useOrganizationList({ userMemberships: organizationListParams.userMemberships, }); const { organization } = useOrganization(); @@ -89,7 +89,7 @@ export const CreateOrganizationForm = withCardStateProvider((props: CreateOrgani } lastCreatedOrganizationRef.current = organization; - await setActive({ organization }); + await selectOrganization(organization); void userMemberships.revalidate?.(); diff --git a/packages/ui/src/components/OrganizationList/UserMembershipList.tsx b/packages/ui/src/components/OrganizationList/UserMembershipList.tsx index a7dec64439b..31403299b4a 100644 --- a/packages/ui/src/components/OrganizationList/UserMembershipList.tsx +++ b/packages/ui/src/components/OrganizationList/UserMembershipList.tsx @@ -17,7 +17,7 @@ export const MembershipPreview = (props: { organization: OrganizationResource }) const card = useCardState(); const { navigateAfterSelectOrganization } = useOrganizationListContext(); const { t } = useLocalizations(); - const { isLoaded, setActive } = useOrganizationList(); + const { isLoaded, selectOrganization } = useOrganizationList(); if (!isLoaded) { return null; @@ -26,9 +26,7 @@ export const MembershipPreview = (props: { organization: OrganizationResource }) const handleOrganizationClicked = (organization: OrganizationResource) => { return card.runAsync(async () => { try { - await setActive({ - organization, - }); + await selectOrganization(organization); await navigateAfterSelectOrganization(organization); } catch (err: any) { @@ -75,7 +73,7 @@ export const MembershipPreview = (props: { organization: OrganizationResource }) export const PersonalAccountPreview = withCardStateProvider(() => { const card = useCardState(); const { hidePersonal, navigateAfterSelectPersonal } = useOrganizationListContext(); - const { isLoaded, setActive } = useOrganizationList(); + const { isLoaded, selectOrganization } = useOrganizationList(); const { user } = useUser(); if (!user) { @@ -89,9 +87,7 @@ export const PersonalAccountPreview = withCardStateProvider(() => { return; } return card.runAsync(async () => { - await setActive({ - organization: null, - }); + await selectOrganization(null); await navigateAfterSelectPersonal(user); }); diff --git a/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx index 40f39578b47..85c8e384577 100644 --- a/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx @@ -27,7 +27,7 @@ export const OrganizationSwitcherPopover = React.forwardRef { return card .runAsync(() => - setActive({ - organization, + selectOrganization(organization, { redirectUrl: afterSelectOrganizationUrl(organization), }), ) @@ -68,7 +67,7 @@ export const OrganizationSwitcherPopover = React.forwardRef { return card - .runAsync(() => setActive({ organization: null, redirectUrl: afterSelectPersonalUrl(user) })) + .runAsync(() => selectOrganization(null, { redirectUrl: afterSelectPersonalUrl(user) })) .then(close); }; diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx index 2c47d5710dc..3b38c699914 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/ChooseOrganizationScreen.tsx @@ -115,7 +115,7 @@ const MembershipPreview = (props: { organization: OrganizationResource }) => { const card = useCardState(); const { navigateOnSetActive } = useSessionTasksContext(); const { redirectUrlComplete } = useTaskChooseOrganizationContext(); - const { isLoaded, setActive } = useOrganizationList(); + const { isLoaded, selectOrganization } = useOrganizationList(); const { t } = useLocalizations(); if (!isLoaded) { @@ -125,8 +125,7 @@ const MembershipPreview = (props: { organization: OrganizationResource }) => { const handleOrganizationClicked = (organization: OrganizationResource) => { return card.runAsync(async () => { try { - await setActive({ - organization, + await selectOrganization(organization, { navigate: async ({ session }) => { await navigateOnSetActive?.({ session, redirectUrlComplete }); }, diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx index 085b45919d9..4f5ee94d405 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx @@ -29,7 +29,7 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = const card = useCardState(); const { navigateOnSetActive } = useSessionTasksContext(); const { redirectUrlComplete } = useTaskChooseOrganizationContext(); - const { createOrganization, isLoaded, setActive } = useOrganizationList({ + const { createOrganization, isLoaded, selectOrganization } = useOrganizationList({ userMemberships: organizationListParams.userMemberships, }); const { organizationSettings } = useEnvironment(); @@ -73,8 +73,7 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) = await organization.setLogo({ file: logoFile }); } - await setActive({ - organization, + await selectOrganization(organization, { navigate: async ({ session }) => { await navigateOnSetActive?.({ session, redirectUrlComplete }); }, diff --git a/packages/ui/src/components/SignIn/ResetPasswordSuccess.tsx b/packages/ui/src/components/SignIn/ResetPasswordSuccess.tsx index 254f253c235..04d44a53db9 100644 --- a/packages/ui/src/components/SignIn/ResetPasswordSuccess.tsx +++ b/packages/ui/src/components/SignIn/ResetPasswordSuccess.tsx @@ -3,12 +3,12 @@ import { useCardState, withCardStateProvider } from '@/ui/elements/contexts'; import { Header } from '@/ui/elements/Header'; import { Col, descriptors, localizationKeys, Spinner, Text } from '../../customizables'; -import { useSetSessionWithTimeout } from '../../hooks/useSetSessionWithTimeout'; +import { useSelectSessionWithTimeout } from '../../hooks/useSetSessionWithTimeout'; import { Flex } from '../../primitives'; const ResetPasswordSuccessInternal = () => { const card = useCardState(); - useSetSessionWithTimeout(); + useSelectSessionWithTimeout(); return ( diff --git a/packages/ui/src/hooks/useSetSessionWithTimeout.ts b/packages/ui/src/hooks/useSetSessionWithTimeout.ts index 832ff461637..f6e667bbc6a 100644 --- a/packages/ui/src/hooks/useSetSessionWithTimeout.ts +++ b/packages/ui/src/hooks/useSetSessionWithTimeout.ts @@ -4,9 +4,9 @@ import { useEffect } from 'react'; import { useSignInContext } from '../contexts'; import { useRouter } from '../router'; -export const useSetSessionWithTimeout = (delay = 2000) => { +export const useSelectSessionWithTimeout = (delay = 2000) => { const { queryString } = useRouter(); - const { setActive } = useClerk(); + const { selectSession } = useClerk(); const { afterSignInUrl } = useSignInContext(); useEffect(() => { @@ -15,7 +15,7 @@ export const useSetSessionWithTimeout = (delay = 2000) => { const createdSessionId = queryParams.get('createdSessionId'); if (createdSessionId) { timeoutId = setTimeout(() => { - void setActive({ session: createdSessionId, redirectUrl: afterSignInUrl }); + void selectSession(createdSessionId, { redirectUrl: afterSignInUrl }); }, delay); } @@ -24,5 +24,10 @@ export const useSetSessionWithTimeout = (delay = 2000) => { clearTimeout(timeoutId); } }; - }, [setActive, afterSignInUrl, queryString]); + }, [selectSession, afterSignInUrl, queryString]); }; + +/** + * @deprecated Use `useSelectSessionWithTimeout` instead. + */ +export const useSetSessionWithTimeout = useSelectSessionWithTimeout; diff --git a/packages/vue/src/composables/useSessionList.ts b/packages/vue/src/composables/useSessionList.ts index 76a33280fa4..bab791e4fd7 100644 --- a/packages/vue/src/composables/useSessionList.ts +++ b/packages/vue/src/composables/useSessionList.ts @@ -36,7 +36,7 @@ export const useSessionList: UseSessionList = () => { const result = computed(() => { if (!clientCtx.value) { - return { isLoaded: false, sessions: undefined, selectSession: undefined, setActive: undefined }; + return { isLoaded: false, sessions: undefined, selectSession: undefined }; } return { @@ -44,8 +44,6 @@ export const useSessionList: UseSessionList = () => { sessions: clientCtx.value.sessions, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion selectSession: clerk.value!.selectSession, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - setActive: clerk.value!.setActive, }; }); diff --git a/packages/vue/src/composables/useSignIn.ts b/packages/vue/src/composables/useSignIn.ts index b0f02745aae..6513f5f2b49 100644 --- a/packages/vue/src/composables/useSignIn.ts +++ b/packages/vue/src/composables/useSignIn.ts @@ -41,14 +41,13 @@ export const useSignIn: UseSignIn = () => { const result = computed(() => { if (!clerk.value || !clientCtx.value) { - return { isLoaded: false, signIn: undefined, selectSession: undefined, setActive: undefined }; + return { isLoaded: false, signIn: undefined, selectSession: undefined }; } return { isLoaded: true, signIn: clientCtx.value.signIn, selectSession: clerk.value.selectSession, - setActive: clerk.value.setActive, }; }); diff --git a/packages/vue/src/composables/useSignUp.ts b/packages/vue/src/composables/useSignUp.ts index bb17cc21d1a..7e175b27b5b 100644 --- a/packages/vue/src/composables/useSignUp.ts +++ b/packages/vue/src/composables/useSignUp.ts @@ -41,14 +41,13 @@ export const useSignUp: UseSignUp = () => { const result = computed(() => { if (!clerk.value || !clientCtx.value) { - return { isLoaded: false, signUp: undefined, selectSession: undefined, setActive: undefined }; + return { isLoaded: false, signUp: undefined, selectSession: undefined }; } return { isLoaded: true, signUp: clientCtx.value.signUp, selectSession: clerk.value.selectSession, - setActive: clerk.value.setActive, }; }); From 95e325f560b65e1a54c739c1abb210ef3e820979 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 22 Jan 2026 10:51:11 -0600 Subject: [PATCH 3/5] chore: add changeset for setActive split --- .../split-setactive-into-select-methods.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .changeset/split-setactive-into-select-methods.md diff --git a/.changeset/split-setactive-into-select-methods.md b/.changeset/split-setactive-into-select-methods.md new file mode 100644 index 00000000000..f67a58f817e --- /dev/null +++ b/.changeset/split-setactive-into-select-methods.md @@ -0,0 +1,73 @@ +--- +'@clerk/clerk-js': major +'@clerk/shared': major +'@clerk/react': major +'@clerk/nextjs': major +'@clerk/vue': major +'@clerk/expo': major +'@clerk/ui': major +--- + +Split `setActive` into purpose-specific methods: `selectSession` and `selectOrganization` + +## Breaking Changes + +### Hook Return Types + +The following hooks no longer return `setActive`. Use the new purpose-specific methods instead: + +- `useSignIn()` - now returns `selectSession` instead of `setActive` +- `useSignUp()` - now returns `selectSession` instead of `setActive` +- `useSessionList()` - now returns `selectSession` instead of `setActive` +- `useOrganizationList()` - now returns `selectOrganization` instead of `setActive` + +### Migration + +**Before:** +```tsx +const { setActive } = useSignIn(); +await setActive({ session: createdSessionId }); + +const { setActive } = useOrganizationList(); +await setActive({ organization: org }); +``` + +**After:** +```tsx +const { selectSession } = useSignIn(); +await selectSession(createdSessionId); + +const { selectOrganization } = useOrganizationList(); +await selectOrganization(org); +``` + +### New Methods on Clerk Object + +Two new methods are available on the Clerk object: + +- `clerk.selectSession(session, options?)` - For session selection (sign-in, sign-up, multi-session switching) +- `clerk.selectOrganization(organization, options?)` - For organization selection (org switching, personal workspace) + +### Options + +Both methods accept an options object: + +```tsx +// selectSession options +await selectSession(session, { + redirectUrl?: string; + navigate?: ({ session }) => void | Promise; +}); + +// selectOrganization options +await selectOrganization(organization, { + redirectUrl?: string; + navigate?: ({ session, organization }) => void | Promise; +}); +``` + +### Internal Window Hooks (Next.js) + +The internal window hooks have been renamed: +- `__internal_onBeforeSetActive` → `__internal_onBeforeSelectSession` +- `__internal_onAfterSetActive` → `__internal_onAfterSelectSession` From 1fbbd70dfc4bc74478fa09568bee213a90399016 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 22 Jan 2026 11:00:37 -0600 Subject: [PATCH 4/5] feat(core): Remove setActive from public API, migrate remaining usages --- .../src/core/resources/BillingCheckout.ts | 2 +- .../clerk-js/src/core/resources/SignIn.ts | 2 +- .../clerk-js/src/core/resources/SignUp.ts | 2 +- packages/clerk-js/src/test/mock-helpers.ts | 3 ++- .../src/hooks/useSignInWithGoogle.shared.ts | 22 +++++++++---------- .../src/hooks/useSignInWithGoogle.types.ts | 4 ++-- packages/shared/src/types/billing.ts | 4 ++-- packages/shared/src/types/clerk.ts | 10 --------- packages/shared/src/types/signInFuture.ts | 4 ++-- packages/shared/src/types/signUpFuture.ts | 4 ++-- packages/testing/src/common/helpers-utils.ts | 12 ++++------ .../__tests__/OrganizationSwitcher.test.tsx | 17 +++++--------- .../useCoreOrganizationList.test.tsx | 2 +- packages/ui/src/test/mock-helpers.ts | 3 ++- 14 files changed, 37 insertions(+), 54 deletions(-) diff --git a/packages/clerk-js/src/core/resources/BillingCheckout.ts b/packages/clerk-js/src/core/resources/BillingCheckout.ts index 0dbadd220fa..367c4ed6399 100644 --- a/packages/clerk-js/src/core/resources/BillingCheckout.ts +++ b/packages/clerk-js/src/core/resources/BillingCheckout.ts @@ -204,7 +204,7 @@ export class CheckoutFlow implements CheckoutFlowResourceNonStrict { throw new Error('Clerk: `confirm()` must be called before `finalize()`'); } - await BillingCheckout.clerk.setActive({ session: BillingCheckout.clerk.session?.id, navigate }); + await BillingCheckout.clerk.selectSession(BillingCheckout.clerk.session?.id ?? null, { navigate }); }); } diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index 2ceec76dd3a..3cea1a4cd4f 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -1243,7 +1243,7 @@ class SignInFuture implements SignInFutureResource { } this.#hasBeenFinalized = true; - await SignIn.clerk.setActive({ session: this.#resource.createdSessionId, navigate }); + await SignIn.clerk.selectSession(this.#resource.createdSessionId, { navigate }); }); } diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 53d3b0647d9..04ed76b2d35 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -972,7 +972,7 @@ class SignUpFuture implements SignUpFutureResource { } this.#hasBeenFinalized = true; - await SignUp.clerk.setActive({ session: this.#resource.createdSessionId, navigate }); + await SignUp.clerk.selectSession(this.#resource.createdSessionId, { navigate }); }); } } diff --git a/packages/clerk-js/src/test/mock-helpers.ts b/packages/clerk-js/src/test/mock-helpers.ts index d76dea115bb..387e236c626 100644 --- a/packages/clerk-js/src/test/mock-helpers.ts +++ b/packages/clerk-js/src/test/mock-helpers.ts @@ -99,7 +99,8 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked; diff --git a/packages/expo/src/hooks/useSignInWithGoogle.shared.ts b/packages/expo/src/hooks/useSignInWithGoogle.shared.ts index e7f4ef97f6b..254abc5b54d 100644 --- a/packages/expo/src/hooks/useSignInWithGoogle.shared.ts +++ b/packages/expo/src/hooks/useSignInWithGoogle.shared.ts @@ -1,6 +1,6 @@ import { useClerk } from '@clerk/react'; import { isClerkAPIResponseError } from '@clerk/shared/error'; -import type { ClientResource, SetActive } from '@clerk/shared/types'; +import type { ClientResource, SelectSessionHook } from '@clerk/shared/types'; import { ClerkGoogleOneTapSignIn, isErrorWithCode, isSuccessResponse } from '../google-one-tap'; import { errorThrower } from '../utils/errors'; @@ -16,7 +16,7 @@ export type GoogleClientIds = { export type GoogleAuthenticationFlowContext = { client: ClientResource; - setActive: SetActive; + selectSession: SelectSessionHook; }; type PlatformConfig = { @@ -64,12 +64,12 @@ export function createUseSignInWithGoogle(platformConfig: PlatformConfig) { async function startGoogleAuthenticationFlow( startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams, ): Promise { - const { client, loaded, setActive } = clerk; + const { client, loaded, selectSession } = clerk; if (!loaded || !client) { return { createdSessionId: null, - setActive, + selectSession, }; } @@ -89,7 +89,7 @@ export function createUseSignInWithGoogle(platformConfig: PlatformConfig) { } return executeGoogleAuthenticationFlow( - { client, setActive }, + { client, selectSession }, { webClientId, iosClientId }, startGoogleAuthenticationFlowParams, ); @@ -111,7 +111,7 @@ export async function executeGoogleAuthenticationFlow( clientIds: GoogleClientIds, params?: StartGoogleAuthenticationFlowParams, ): Promise { - const { client, setActive } = context; + const { client, selectSession } = context; const { signIn, signUp } = client; // Configure Google Sign-In with client IDs @@ -131,7 +131,7 @@ export async function executeGoogleAuthenticationFlow( if (!isSuccessResponse(response)) { return { createdSessionId: null, - setActive, + selectSession, signIn, signUp, }; @@ -158,7 +158,7 @@ export async function executeGoogleAuthenticationFlow( return { createdSessionId: signUp.createdSessionId, - setActive, + selectSession, signIn, signUp, }; @@ -167,7 +167,7 @@ export async function executeGoogleAuthenticationFlow( // User exists - return the SignIn session return { createdSessionId: signIn.createdSessionId, - setActive, + selectSession, signIn, signUp, }; @@ -186,7 +186,7 @@ export async function executeGoogleAuthenticationFlow( return { createdSessionId: signUp.createdSessionId, - setActive, + selectSession, signIn, signUp, }; @@ -200,7 +200,7 @@ export async function executeGoogleAuthenticationFlow( if (isErrorWithCode(error) && error.code === 'SIGN_IN_CANCELLED') { return { createdSessionId: null, - setActive, + selectSession, signIn, signUp, }; diff --git a/packages/expo/src/hooks/useSignInWithGoogle.types.ts b/packages/expo/src/hooks/useSignInWithGoogle.types.ts index 522f1a12385..2b5ea16fd72 100644 --- a/packages/expo/src/hooks/useSignInWithGoogle.types.ts +++ b/packages/expo/src/hooks/useSignInWithGoogle.types.ts @@ -1,4 +1,4 @@ -import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; +import type { SelectSessionHook, SignInResource, SignUpResource } from '@clerk/shared/types'; export type StartGoogleAuthenticationFlowParams = { unsafeMetadata?: SignUpUnsafeMetadata; @@ -6,7 +6,7 @@ export type StartGoogleAuthenticationFlowParams = { export type StartGoogleAuthenticationFlowReturnType = { createdSessionId: string | null; - setActive?: SetActive; + selectSession?: SelectSessionHook; signIn?: SignInResource; signUp?: SignUpResource; }; diff --git a/packages/shared/src/types/billing.ts b/packages/shared/src/types/billing.ts index fd6124f1337..d83b6f51e77 100644 --- a/packages/shared/src/types/billing.ts +++ b/packages/shared/src/types/billing.ts @@ -1,6 +1,6 @@ import type { ClerkError } from '@/errors/clerkError'; -import type { SetActiveNavigate } from './clerk'; +import type { SelectSessionNavigate } from './clerk'; import type { DeletedObjectResource } from './deletedObject'; import type { ClerkPaginatedResponse, ClerkPaginationParams } from './pagination'; import type { ClerkResource } from './resource'; @@ -983,7 +983,7 @@ type CheckoutFlowInitialized = { type CheckoutPropertiesPerStatus = CheckoutFlowUninitialized | CheckoutFlowInitialized; export interface CheckoutFlowFinalizeParams { - navigate: SetActiveNavigate; + navigate: SelectSessionNavigate; } /** diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 2d02c68fd30..7ba5e40ad29 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -769,16 +769,6 @@ export interface Clerk { */ __internal_addNavigationListener: (callback: () => void) => UnsubscribeCallback; - /** - * Set the active session and Organization explicitly. - * - * If the session param is `null`, the active session is deleted. - * In a similar fashion, if the organization param is `null`, the current organization is removed as active. - * - * @deprecated Use `selectSession()` or `selectOrganization()` instead. - */ - setActive: SetActive; - /** * Select a session to make active. * diff --git a/packages/shared/src/types/signInFuture.ts b/packages/shared/src/types/signInFuture.ts index fa673bb4d66..01879ed2f57 100644 --- a/packages/shared/src/types/signInFuture.ts +++ b/packages/shared/src/types/signInFuture.ts @@ -1,5 +1,5 @@ import type { ClerkError } from '../errors/clerkError'; -import type { SetActiveNavigate } from './clerk'; +import type { SelectSessionNavigate } from './clerk'; import type { PhoneCodeChannel } from './phoneCodeChannel'; import type { SignInFirstFactor, SignInSecondFactor, SignInStatus, UserData } from './signInCommon'; import type { OAuthStrategy, PasskeyStrategy, Web3Strategy } from './strategies'; @@ -273,7 +273,7 @@ export interface SignInFuturePasskeyParams { } export interface SignInFutureFinalizeParams { - navigate?: SetActiveNavigate; + navigate?: SelectSessionNavigate; } /** diff --git a/packages/shared/src/types/signUpFuture.ts b/packages/shared/src/types/signUpFuture.ts index d44ba2e534c..4d15b9a5548 100644 --- a/packages/shared/src/types/signUpFuture.ts +++ b/packages/shared/src/types/signUpFuture.ts @@ -1,5 +1,5 @@ import type { ClerkError } from '../errors/clerkError'; -import type { SetActiveNavigate } from './clerk'; +import type { SelectSessionNavigate } from './clerk'; import type { PhoneCodeChannel } from './phoneCodeChannel'; import type { SignUpField, SignUpIdentificationField, SignUpStatus } from './signUpCommon'; import type { Web3Strategy } from './strategies'; @@ -249,7 +249,7 @@ export interface SignUpFutureWeb3Params extends SignUpFutureAdditionalParams { } export interface SignUpFutureFinalizeParams { - navigate?: SetActiveNavigate; + navigate?: SelectSessionNavigate; } /** diff --git a/packages/testing/src/common/helpers-utils.ts b/packages/testing/src/common/helpers-utils.ts index 923cf3a194d..e1dea1fc432 100644 --- a/packages/testing/src/common/helpers-utils.ts +++ b/packages/testing/src/common/helpers-utils.ts @@ -15,9 +15,7 @@ export const signInHelper = async ({ signInParams, windowObject }: SignInHelperP switch (signInParams.strategy) { case 'password': { const res = await signIn.create(signInParams); - await w.Clerk.setActive({ - session: res.createdSessionId, - }); + await w.Clerk.selectSession(res.createdSessionId); break; } @@ -28,9 +26,7 @@ export const signInHelper = async ({ signInParams, windowObject }: SignInHelperP }); if (res.status === 'complete') { - await w.Clerk.setActive({ - session: res.createdSessionId, - }); + await w.Clerk.selectSession(res.createdSessionId); } else { throw new Error(`Sign-in with ticket failed. Status: ${res.status}`); } @@ -66,7 +62,7 @@ export const signInHelper = async ({ signInParams, windowObject }: SignInHelperP }); if (signInAttempt.status === 'complete') { - await w.Clerk.setActive({ session: signInAttempt.createdSessionId }); + await w.Clerk.selectSession(signInAttempt.createdSessionId); } else { throw new Error(`Status is ${signInAttempt.status}`); } @@ -105,7 +101,7 @@ export const signInHelper = async ({ signInParams, windowObject }: SignInHelperP }); if (signInAttempt.status === 'complete') { - await w.Clerk.setActive({ session: signInAttempt.createdSessionId }); + await w.Clerk.selectSession(signInAttempt.createdSessionId); } else { throw new Error(`Status is ${signInAttempt.status}`); } diff --git a/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx index 9334d0a812b..3204e8fa9bd 100644 --- a/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx +++ b/packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx @@ -564,7 +564,7 @@ describe('OrganizationSwitcher', () => { }), ); - fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve()); + fixtures.clerk.selectOrganization.mockReturnValueOnce(Promise.resolve()); props.setProps({ hidePersonal: true }); const { getByRole, getByText, userEvent } = render(, { wrapper }); @@ -576,12 +576,11 @@ describe('OrganizationSwitcher', () => { }); await userEvent.click(getByText('Org2')); - expect(fixtures.clerk.setActive).toHaveBeenCalledWith( + expect(fixtures.clerk.selectOrganization).toHaveBeenCalledWith( expect.objectContaining({ - organization: expect.objectContaining({ - name: 'Org2', - }), + name: 'Org2', }), + expect.anything(), ); }); @@ -597,7 +596,7 @@ describe('OrganizationSwitcher', () => { }); }); - fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve()); + fixtures.clerk.selectOrganization.mockReturnValueOnce(Promise.resolve()); const { getByRole, getByText, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button')); expect(fixtures.clerk.user?.getOrganizationMemberships).toHaveBeenCalledTimes(1); @@ -607,11 +606,7 @@ describe('OrganizationSwitcher', () => { }); await userEvent.click(getByText(/Personal account/i)); - expect(fixtures.clerk.setActive).toHaveBeenCalledWith( - expect.objectContaining({ - organization: null, - }), - ); + expect(fixtures.clerk.selectOrganization).toHaveBeenCalledWith(null, expect.anything()); }); }); diff --git a/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx b/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx index dbe8147240e..6ab210d298d 100644 --- a/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx +++ b/packages/ui/src/hooks/__tests__/useCoreOrganizationList.test.tsx @@ -38,7 +38,7 @@ describe('useOrganizationList', () => { const { result } = renderHook(() => useOrganizationList(), { wrapper }); expect(result.current.isLoaded).toBe(true); - expect(result.current.setActive).toBeDefined(); + expect(result.current.selectOrganization).toBeDefined(); expect(result.current.createOrganization).toBeDefined(); expect(result.current.userInvitations).toEqual( diff --git a/packages/ui/src/test/mock-helpers.ts b/packages/ui/src/test/mock-helpers.ts index 35bcb54a8f6..a0134cc9fe6 100644 --- a/packages/ui/src/test/mock-helpers.ts +++ b/packages/ui/src/test/mock-helpers.ts @@ -99,7 +99,8 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked; From a6880dd701a756dd4616e5d0755410fa0a091ed3 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 22 Jan 2026 11:00:59 -0600 Subject: [PATCH 5/5] docs: Update changeset for setActive removal --- .../split-setactive-into-select-methods.md | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.changeset/split-setactive-into-select-methods.md b/.changeset/split-setactive-into-select-methods.md index f67a58f817e..4251c774a04 100644 --- a/.changeset/split-setactive-into-select-methods.md +++ b/.changeset/split-setactive-into-select-methods.md @@ -6,15 +6,23 @@ '@clerk/vue': major '@clerk/expo': major '@clerk/ui': major +'@clerk/testing': major --- -Split `setActive` into purpose-specific methods: `selectSession` and `selectOrganization` +Replace `setActive` with purpose-specific methods: `selectSession` and `selectOrganization` ## Breaking Changes +### `setActive` Removed from Public API + +The `setActive` method has been removed from the Clerk object and all hooks. Use the new purpose-specific methods instead: + +- `clerk.selectSession(session, options?)` - For session selection +- `clerk.selectOrganization(organization, options?)` - For organization selection + ### Hook Return Types -The following hooks no longer return `setActive`. Use the new purpose-specific methods instead: +The following hooks no longer return `setActive`: - `useSignIn()` - now returns `selectSession` instead of `setActive` - `useSignUp()` - now returns `selectSession` instead of `setActive` @@ -66,8 +74,19 @@ await selectOrganization(organization, { }); ``` +### Expo Hooks + +The `StartGoogleAuthenticationFlowReturnType` and `StartAppleAuthenticationFlowReturnType` types now return `selectSession` instead of `setActive`. + ### Internal Window Hooks (Next.js) The internal window hooks have been renamed: - `__internal_onBeforeSetActive` → `__internal_onBeforeSelectSession` - `__internal_onAfterSetActive` → `__internal_onAfterSelectSession` + +### Types Removed + +The following types have been removed from the public API: +- `SetActive` +- `SetActiveParams` +- `SetActiveNavigate` (use `SelectSessionNavigate` instead)