@clerk/shared
Version:
Internal package utils used by the Clerk SDKs
3,723 lines • 122 kB
JavaScript
import { isReverificationHint, reverificationError } from "../authorization-errors.mjs";
import { validateReverificationConfig } from "../authorization.mjs";
import { deriveFromSsrInitialState } from "../deriveState.mjs";
import { t as ClerkRuntimeError } from "../_chunks/clerkRuntimeError-DlesLWqO.mjs";
import { C as isClerkAPIResponseError } from "../_chunks/error-uYOdvTDm.mjs";
import { logger } from "../logger.mjs";
import { getCurrentOrganizationMembership } from "../organization.mjs";
import { a as eventMethodCalled } from "../_chunks/telemetry-9C6N5ppw.mjs";
import { t as createDeferredPromise } from "../_chunks/createDeferredPromise-DNC587Fa.mjs";
import { clerkCoreErrorNoClerkSingleton } from "../internal/clerk-js/errors.mjs";
import * as React$1 from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
import { InfiniteQueryObserver, QueryClient, QueryObserver, noop, notifyManager } from "@tanstack/query-core";
import { dequal } from "dequal";
//#region src/react/hooks/createContextAndHook.ts
/**
* Assert that the context value exists, otherwise throw an error.
*
* @internal
*/
function assertContextExists(contextVal, msgOrCtx) {
if (!contextVal) throw typeof msgOrCtx === "string" ? new Error(msgOrCtx) : /* @__PURE__ */ new Error(`${msgOrCtx.displayName} not found`);
}
/**
* Create and return a Context and two hooks that return the context value.
* The Context type is derived from the type passed in by the user.
*
* The first hook returned guarantees that the context exists so the returned value is always `CtxValue`
* The second hook makes no guarantees, so the returned value can be `CtxValue | undefined`
*
* @internal
*/
const createContextAndHook = (displayName, options) => {
const { assertCtxFn = assertContextExists } = options || {};
const Ctx = React.createContext(void 0);
Ctx.displayName = displayName;
const useCtx = () => {
const ctx = React.useContext(Ctx);
assertCtxFn(ctx, `${displayName} not found`);
return ctx.value;
};
const useCtxWithoutGuarantee = () => {
const ctx = React.useContext(Ctx);
return ctx ? ctx.value : {};
};
return [
Ctx,
useCtx,
useCtxWithoutGuarantee
];
};
//#endregion
//#region src/react/contexts.tsx
const [ClerkInstanceContext, useClerkInstanceContext] = createContextAndHook("ClerkInstanceContext");
const [InitialStateContext, _useInitialStateContext] = createContextAndHook("InitialStateContext");
/**
* Provides initial Clerk state (session, user, organization data) from server-side rendering
* to child components via React context.
*
* Passing in a promise is only supported for React >= 19.
*
* The initialState is snapshotted on mount and cannot change during the component lifecycle.
*
* Note that different parts of the React tree can use separate InitialStateProvider instances
* with different initialState values if needed.
*/
function InitialStateProvider({ children, initialState }) {
const [initialStateSnapshot] = useState(initialState);
const initialStateCtx = React.useMemo(() => ({ value: initialStateSnapshot }), [initialStateSnapshot]);
return /* @__PURE__ */ React.createElement(InitialStateContext.Provider, { value: initialStateCtx }, children);
}
function useInitialStateContext() {
const initialState = _useInitialStateContext();
if (initialState instanceof Promise) if ("use" in React && typeof React.use === "function") return React.use(initialState);
else throw new Error("initialState cannot be a promise if React version is less than 19");
return initialState;
}
const OptionsContext = React.createContext({});
const [CheckoutContext, useCheckoutContext] = createContextAndHook("CheckoutContext");
const __experimental_CheckoutProvider = ({ children, ...rest }) => {
return /* @__PURE__ */ React.createElement(CheckoutContext.Provider, { value: { value: rest } }, children);
};
/**
* @internal
*/
function useOptionsContext() {
const context = React.useContext(OptionsContext);
if (context === void 0) throw new Error("useOptions must be used within an OptionsContext");
return context;
}
/**
* @internal
*/
function useAssertWrappedByClerkProvider(displayNameOrFn) {
if (!React.useContext(ClerkInstanceContext)) {
if (typeof displayNameOrFn === "function") {
displayNameOrFn();
return;
}
throw new Error(`${displayNameOrFn} can only be used within the <ClerkProvider /> component.
Possible fixes:
1. Ensure that the <ClerkProvider /> is correctly wrapping your application where this component is used.
2. Check for multiple versions of the \`@clerk/shared\` package in your project. Use a tool like \`npm ls @clerk/shared\` to identify multiple versions, and update your dependencies to only rely on one.
Learn more: https://clerk.com/docs/components/clerk-provider`.trim());
}
}
//#endregion
//#region src/react/stable-keys.ts
const USER_MEMBERSHIPS_KEY = "userMemberships";
const USER_INVITATIONS_KEY = "userInvitations";
const USER_SUGGESTIONS_KEY = "userSuggestions";
const DOMAINS_KEY = "domains";
const MEMBERSHIP_REQUESTS_KEY = "membershipRequests";
const MEMBERSHIPS_KEY = "memberships";
const INVITATIONS_KEY = "invitations";
const API_KEYS_KEY = "apiKeys";
const ORGANIZATION_CREATION_DEFAULTS_KEY = "organizationCreationDefaults";
const OAUTH_CONSENT_INFO_KEY = "oauthConsentInfo";
const PLANS_KEY = "billing-plans";
const SUBSCRIPTION_KEY = "billing-subscription";
const PAYMENT_METHODS_KEY = "billing-payment-methods";
const PAYMENT_ATTEMPTS_KEY = "billing-payment-attempts";
const STATEMENTS_KEY = "billing-statements";
const CREDIT_BALANCE_KEY = "billing-credit-balance";
const STABLE_KEYS = {
USER_MEMBERSHIPS_KEY,
USER_INVITATIONS_KEY,
USER_SUGGESTIONS_KEY,
DOMAINS_KEY,
MEMBERSHIP_REQUESTS_KEY,
MEMBERSHIPS_KEY,
INVITATIONS_KEY,
PLANS_KEY,
SUBSCRIPTION_KEY,
PAYMENT_METHODS_KEY,
PAYMENT_ATTEMPTS_KEY,
STATEMENTS_KEY,
API_KEYS_KEY,
ORGANIZATION_CREATION_DEFAULTS_KEY,
OAUTH_CONSENT_INFO_KEY,
CREDIT_BALANCE_KEY
};
/**
* Internal stable keys for queries only used by our UI components.
* These keys are not used by the hooks themselves.
*/
const PAYMENT_ATTEMPT_KEY = "billing-payment-attempt";
const BILLING_PLANS_KEY = "billing-plan";
const BILLING_STATEMENTS_KEY = "billing-statement";
const USER_ENTERPRISE_CONNECTIONS_KEY = "userEnterpriseConnections";
const ENTERPRISE_CONNECTION_TEST_RUNS_KEY = "enterpriseConnectionTestRuns";
const ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY = "organizationEnterpriseConnections";
const ORGANIZATION_ENTERPRISE_CONNECTION_TEST_RUNS_KEY = "organizationEnterpriseConnectionTestRuns";
const ORGANIZATION_DOMAINS_KEY = "organizationDomains";
const CREDIT_HISTORY_KEY = "billing-credit-history";
const INTERNAL_STABLE_KEYS = {
PAYMENT_ATTEMPT_KEY,
BILLING_PLANS_KEY,
BILLING_STATEMENTS_KEY,
CREDIT_HISTORY_KEY,
USER_ENTERPRISE_CONNECTIONS_KEY,
ENTERPRISE_CONNECTION_TEST_RUNS_KEY,
ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY,
ORGANIZATION_ENTERPRISE_CONNECTION_TEST_RUNS_KEY,
ORGANIZATION_DOMAINS_KEY
};
//#endregion
//#region src/react/hooks/createCacheKeys.ts
/**
* @internal
*/
function createCacheKeys(params) {
return {
queryKey: [
params.stablePrefix,
params.authenticated,
params.tracked,
params.untracked
],
invalidationKey: [
params.stablePrefix,
params.authenticated,
params.tracked
],
stableKey: params.stablePrefix,
authenticated: params.authenticated
};
}
//#endregion
//#region src/react/query/keep-previous-data.ts
/**
* @internal
*/
function defineKeepPreviousDataFn(enabled) {
if (enabled) return function KeepPreviousDataFn(previousData) {
return previousData;
};
}
//#endregion
//#region src/react/query/clerk-query-client.ts
/**
* The QueryClient backing every clerk-query hook. Owned by `@clerk/shared` so the
* `QueryObserver` that observes it and the `Query` objects inside it always
* resolve to the same `@tanstack/query-core` (no cross-bundle drift between
* the consumer-side `@clerk/shared` and the production CDN `clerk-js` bundle).
*
* Lazily instantiated on the client only. Server-side renders return
* `undefined` so per-request renders never share a cache across requests.
*/
let clerkQueryClient;
let initialized = false;
function getClerkQueryClient() {
if (typeof window === "undefined") return;
if (!initialized) {
clerkQueryClient = new QueryClient();
initialized = true;
}
return clerkQueryClient;
}
/**
* Test-only: install a custom client (for deterministic defaults like
* `staleTime: Infinity`) or pass `undefined` to simulate the "no client"
* state without triggering lazy creation on subsequent reads.
*/
function __setClerkQueryClientForTest(client) {
clerkQueryClient = client;
initialized = true;
}
/**
* Test-only: build and install a fresh `QueryClient` with deterministic
* defaults (no retries, infinite stale time, no refetching). Returns the
* client so the spec can read/write its cache directly.
*
* Avoids forcing every test consumer to depend on `@tanstack/query-core`.
*/
function __createClerkTestQueryClient() {
const client = new QueryClient({ defaultOptions: { queries: {
retry: false,
staleTime: Infinity,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false
} } });
__setClerkQueryClientForTest(client);
return client;
}
/**
* Test-only: clear both the override and the initialization flag so the
* next read lazy-creates a fresh client.
*/
function __resetClerkQueryClientForTest() {
clerkQueryClient = void 0;
initialized = false;
}
//#endregion
//#region src/react/query/use-clerk-query-client.ts
/**
* Creates a recursively self-referential Proxy that safely handles:
* - Arbitrary property access (e.g., obj.any.prop.path)
* - Function calls at any level (e.g., obj.a().b.c())
* - Construction (e.g., new obj.a.b())
*
* Always returns itself to allow infinite chaining without throwing.
*/
function createRecursiveProxy(label) {
const callableTarget = function noop() {};
let self;
self = new Proxy(callableTarget, {
get(_target, prop) {
if (prop === "then") return;
if (prop === "toString") return () => `[${label}]`;
if (prop === Symbol.toPrimitive) return () => 0;
return self;
},
apply() {
return self;
},
construct() {
return self;
},
has() {
return false;
},
set() {
return false;
}
});
return self;
}
const mockQueryClient = createRecursiveProxy("ClerkMockQueryClient");
/**
* Returns `[client, isLoaded]`. The real client is owned by `@clerk/shared`
* and lazily instantiated on the browser only — SSR returns the proxy mock
* + `isLoaded: false` so per-request renders never share a query cache.
*/
const useClerkQueryClient = () => {
const client = getClerkQueryClient();
return [client ?? mockQueryClient, Boolean(client)];
};
//#endregion
//#region src/react/query/useBaseQuery.ts
/**
* Stripped down version of useBaseQuery from @tanstack/query-core.
* This implementation allows for an observer to be created every time a query client changes.
*/
/**
* An alternative `useBaseQuery` implementation that allows for an observer to be created every time a query client changes.
*
* @internal
*/
function useBaseQuery(options, Observer) {
const [client, isQueryClientLoaded] = useClerkQueryClient();
const defaultedOptions = isQueryClientLoaded ? client.defaultQueryOptions(options) : options;
defaultedOptions._optimisticResults = "optimistic";
const observer = React$1.useMemo(() => {
return new Observer(client, defaultedOptions);
}, [client]);
const result = observer.getOptimisticResult(defaultedOptions);
const shouldSubscribe = options.subscribed !== false;
React$1.useSyncExternalStore(React$1.useCallback((onStoreChange) => {
const unsubscribe = shouldSubscribe ? observer.subscribe(notifyManager.batchCalls(onStoreChange)) : noop;
observer.updateResult();
return unsubscribe;
}, [observer, shouldSubscribe]), () => observer.getCurrentResult(), () => observer.getCurrentResult());
React$1.useEffect(() => {
observer.setOptions(defaultedOptions);
}, [defaultedOptions, observer]);
if (!isQueryClientLoaded) return {
data: void 0,
error: null,
isLoading: options.enabled !== false,
isFetching: false,
status: "pending"
};
return !defaultedOptions.notifyOnChangeProps ? observer.trackResult(result) : result;
}
//#endregion
//#region src/react/query/useInfiniteQuery.ts
/**
*
*/
function useClerkInfiniteQuery(options) {
return useBaseQuery(options, InfiniteQueryObserver);
}
//#endregion
//#region src/react/query/useQuery.ts
/**
*
*/
function useClerkQuery(options) {
return useBaseQuery(options, QueryObserver);
}
//#endregion
//#region src/react/hooks/usePreviousValue.ts
/**
* A hook that retains the previous value of a primitive type.
* It uses a ref to prevent causing unnecessary re-renders.
*
* @internal
*
* @example
* ```
* Render 1: value = 'A' → returns null
* Render 2: value = 'B' → returns 'A'
* Render 3: value = 'B' → returns 'A'
* Render 4: value = 'B' → returns 'A'
* Render 5: value = 'C' → returns 'B'
* ```
*/
function usePreviousValue(value) {
const currentRef = useRef(value);
const previousRef = useRef(null);
if (currentRef.current !== value) {
previousRef.current = currentRef.current;
currentRef.current = value;
}
return previousRef.current;
}
//#endregion
//#region src/react/hooks/useClearQueriesOnSignOut.ts
const withInfiniteKey = (key) => [key, `${key}-inf`];
/**
* Clears React Query caches associated with the given stable prefixes when
* the authenticated state transitions from signed-in to signed-out.
*
* @internal
*/
function useClearQueriesOnSignOut(options) {
const { isSignedOut, stableKeys, authenticated = true, onCleanup } = options;
const stableKeysRef = useRef(stableKeys);
const [queryClient] = useClerkQueryClient();
const previousIsSignedIn = usePreviousValue(!isSignedOut);
useEffect(() => {
if (authenticated !== true) return;
if (previousIsSignedIn && isSignedOut === true) {
queryClient.removeQueries({ predicate: (query) => {
const [cachedStableKey, queryAuthenticated] = query.queryKey;
return queryAuthenticated === true && typeof cachedStableKey === "string" && (Array.isArray(stableKeysRef.current) ? stableKeysRef.current.includes(cachedStableKey) : stableKeysRef.current === cachedStableKey);
} });
onCleanup?.();
}
}, [
authenticated,
isSignedOut,
previousIsSignedIn,
queryClient
]);
}
//#endregion
//#region src/react/hooks/usePagesOrInfinite.shared.ts
/**
* A hook that safely merges user-provided pagination options with default values.
* It caches initial pagination values (page and size) until component unmount to prevent unwanted rerenders.
*
* @internal
*
* @example
* ```typescript
* // Example 1: With user-provided options
* const userOptions = { initialPage: 2, pageSize: 20, infinite: true };
* const defaults = { initialPage: 1, pageSize: 10, infinite: false };
* useWithSafeValues(userOptions, defaults);
* // Returns { initialPage: 2, pageSize: 20, infinite: true }
*
* // Example 2: With boolean true (use defaults)
* const params = true;
* const defaults = { initialPage: 1, pageSize: 10, infinite: false };
* useWithSafeValues(params, defaults);
* // Returns { initialPage: 1, pageSize: 10, infinite: false }
*
* // Example 3: With undefined options (fallback to defaults)
* const params = undefined;
* const defaults = { initialPage: 1, pageSize: 10, infinite: false };
* useWithSafeValues(params, defaults);
* // Returns { initialPage: 1, pageSize: 10, infinite: false }
* ```
*/
const useWithSafeValues = (params, defaultValues) => {
const shouldUseDefaults = typeof params === "boolean" && params;
const initialPageRef = useRef(shouldUseDefaults ? defaultValues.initialPage : params?.initialPage ?? defaultValues.initialPage);
const pageSizeRef = useRef(shouldUseDefaults ? defaultValues.pageSize : params?.pageSize ?? defaultValues.pageSize);
const newObj = {};
for (const key of Object.keys(defaultValues)) newObj[key] = shouldUseDefaults ? defaultValues[key] : params?.[key] ?? defaultValues[key];
return {
...newObj,
initialPage: initialPageRef.current,
pageSize: pageSizeRef.current
};
};
/**
* Calculates the offset count for pagination based on initial page and page size.
* This represents the number of items to skip before the first page.
*
* @param initialPage - The starting page number (1-based)
* @param pageSize - The number of items per page
* @returns The number of items to offset
*
* @example
* ```typescript
* calculateOffsetCount(1, 10); // Returns 0 (no offset for first page)
* calculateOffsetCount(2, 10); // Returns 10 (skip first 10 items)
* calculateOffsetCount(3, 20); // Returns 40 (skip first 40 items)
* ```
*/
function calculateOffsetCount(initialPage, pageSize) {
return (initialPage - 1) * pageSize;
}
/**
* Calculates the total number of pages based on total count, offset, and page size.
*
* @param totalCount - The total number of items
* @param offsetCount - The number of items to offset (from calculateOffsetCount)
* @param pageSize - The number of items per page
* @returns The total number of pages
*
* @example
* ```typescript
* calculatePageCount(100, 0, 10); // Returns 10
* calculatePageCount(95, 0, 10); // Returns 10 (rounds up)
* calculatePageCount(100, 20, 10); // Returns 8 (100 - 20 = 80 items, 8 pages)
* ```
*/
function calculatePageCount(totalCount, offsetCount, pageSize) {
return Math.ceil((totalCount - offsetCount) / pageSize);
}
/**
* Determines if there is a next page available in non-infinite pagination mode.
*
* @param totalCount - The total number of items
* @param offsetCount - The number of items to offset
* @param currentPage - The current page number (1-based)
* @param pageSize - The number of items per page
* @returns True if there are more items beyond the current page
*
* @example
* ```typescript
* calculateHasNextPage(100, 0, 1, 10); // Returns true (page 1 of 10)
* calculateHasNextPage(100, 0, 10, 10); // Returns false (last page)
* calculateHasNextPage(25, 0, 2, 10); // Returns true (page 2, 5 more items)
* calculateHasNextPage(20, 0, 2, 10); // Returns false (exactly 2 pages)
* ```
*/
function calculateHasNextPage(totalCount, offsetCount, currentPage, pageSize) {
return totalCount - offsetCount > currentPage * pageSize;
}
/**
* Determines if there is a previous page available in non-infinite pagination mode.
*
* @param currentPage - The current page number (1-based)
* @param pageSize - The number of items per page
* @param offsetCount - The number of items to offset
* @returns True if there are pages before the current page
*
* @example
* ```typescript
* calculateHasPreviousPage(1, 10, 0); // Returns false (first page)
* calculateHasPreviousPage(2, 10, 0); // Returns true (can go back to page 1)
* calculateHasPreviousPage(1, 10, 10); // Returns false (first page with offset)
* ```
*/
function calculateHasPreviousPage(currentPage, pageSize, offsetCount) {
return (currentPage - 1) * pageSize > offsetCount;
}
//#endregion
//#region src/react/hooks/usePagesOrInfinite.tsx
const usePagesOrInfinite = (params) => {
const { fetcher, config, keys } = params;
const [paginatedPage, setPaginatedPage] = useState(config.initialPage ?? 1);
const initialPageRef = useRef(config.initialPage ?? 1);
const pageSizeRef = useRef(config.pageSize ?? 10);
const enabled = config.enabled ?? true;
const isSignedIn = config.isSignedIn;
const triggerInfinite = config.infinite ?? false;
const cacheMode = config.__experimental_mode === "cache";
const keepPreviousData = config.keepPreviousData ?? false;
const [queryClient] = useClerkQueryClient();
const queriesEnabled = enabled && Boolean(fetcher) && !cacheMode && isSignedIn !== false;
const [forceUpdateCounter, setForceUpdateCounter] = useState(0);
const forceUpdate = useCallback((updater) => {
setForceUpdateCounter(updater);
}, []);
const pagesQueryKey = useMemo(() => {
const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey;
return [
stablePrefix,
authenticated,
tracked,
{
...untracked,
args: {
...untracked.args,
initialPage: paginatedPage,
pageSize: pageSizeRef.current
}
}
];
}, [keys.queryKey, paginatedPage]);
const singlePageQuery = useClerkQuery({
queryKey: pagesQueryKey,
queryFn: ({ queryKey }) => {
const { args } = queryKey[3];
if (!fetcher) return;
return fetcher(args);
},
staleTime: 6e4,
enabled: queriesEnabled && !triggerInfinite,
placeholderData: defineKeepPreviousDataFn(keepPreviousData)
});
const infiniteQueryKey = useMemo(() => {
const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey;
return [
stablePrefix + "-inf",
authenticated,
tracked,
untracked
];
}, [keys.queryKey]);
const infiniteQuery = useClerkInfiniteQuery({
queryKey: infiniteQueryKey,
initialPageParam: config.initialPage ?? 1,
getNextPageParam: (lastPage, allPages, lastPageParam) => {
const total = lastPage?.total_count ?? 0;
return (allPages.length + (config.initialPage ? config.initialPage - 1 : 0)) * (config.pageSize ?? 10) < total ? lastPageParam + 1 : void 0;
},
queryFn: ({ pageParam, queryKey }) => {
const { args } = queryKey[3];
if (!fetcher) return;
return fetcher({
...args,
initialPage: pageParam,
pageSize: pageSizeRef.current
});
},
staleTime: 6e4,
enabled: queriesEnabled && triggerInfinite
});
useClearQueriesOnSignOut({
isSignedOut: isSignedIn === false,
authenticated: keys.authenticated,
stableKeys: withInfiniteKey(keys.stableKey),
onCleanup: () => {
setPaginatedPage(initialPageRef.current);
Promise.resolve().then(() => forceUpdate((n) => n + 1));
}
});
const { data, count, page } = useMemo(() => {
if (triggerInfinite) {
const cachedData = queryClient.getQueryData(infiniteQueryKey);
const pages = queriesEnabled ? infiniteQuery.data?.pages ?? cachedData?.pages ?? [] : cachedData?.pages ?? [];
const validPages = Array.isArray(pages) ? pages.filter(Boolean) : [];
return {
data: validPages.map((a) => a?.data).flat().filter(Boolean) ?? [],
count: validPages[validPages.length - 1]?.total_count ?? 0,
page: validPages.length > 0 ? validPages.length : initialPageRef.current
};
}
const pageData = queriesEnabled ? singlePageQuery.data ?? queryClient.getQueryData(pagesQueryKey) : queryClient.getQueryData(pagesQueryKey);
return {
data: Array.isArray(pageData?.data) ? pageData.data : [],
count: typeof pageData?.total_count === "number" ? pageData.total_count : 0,
page: paginatedPage
};
}, [
queriesEnabled,
forceUpdateCounter,
triggerInfinite,
infiniteQuery.data?.pages,
singlePageQuery.data,
queryClient,
infiniteQueryKey,
pagesQueryKey,
paginatedPage
]);
const fetchPage = useCallback((numberOrgFn) => {
if (triggerInfinite) {
const next = typeof numberOrgFn === "function" ? numberOrgFn(page) : numberOrgFn;
const targetCount = Math.max(0, next);
const cachedData = queryClient.getQueryData(infiniteQueryKey);
if (targetCount - (infiniteQuery.data?.pages ?? cachedData?.pages ?? []).length > 0) infiniteQuery.fetchNextPage({ cancelRefetch: false });
return;
}
return setPaginatedPage(numberOrgFn);
}, [
infiniteQuery,
page,
triggerInfinite,
queryClient,
infiniteQueryKey
]);
const isLoading = triggerInfinite ? infiniteQuery.isLoading : singlePageQuery.isLoading;
const isFetching = triggerInfinite ? infiniteQuery.isFetching : singlePageQuery.isFetching;
const error = (triggerInfinite ? infiniteQuery.error : singlePageQuery.error) ?? null;
const isError = !!error;
const fetchNext = useCallback(() => {
if (triggerInfinite) {
infiniteQuery.fetchNextPage({ cancelRefetch: false });
return;
}
setPaginatedPage((n) => Math.max(0, n + 1));
}, [infiniteQuery, triggerInfinite]);
const fetchPrevious = useCallback(() => {
if (triggerInfinite) return;
setPaginatedPage((n) => Math.max(0, n - 1));
}, [triggerInfinite]);
const offsetCount = calculateOffsetCount(initialPageRef.current, pageSizeRef.current);
const pageCount = calculatePageCount(count, offsetCount, pageSizeRef.current);
const hasNextPage = triggerInfinite ? Boolean(infiniteQuery.hasNextPage) : calculateHasNextPage(count, offsetCount, page, pageSizeRef.current);
const hasPreviousPage = triggerInfinite ? Boolean(infiniteQuery.hasPreviousPage) : calculateHasPreviousPage(page, pageSizeRef.current, offsetCount);
const setData = (value) => {
if (triggerInfinite) {
queryClient.setQueryData(infiniteQueryKey, (prevValue = {}) => {
const prevPages = Array.isArray(prevValue?.pages) ? prevValue.pages : [];
const nextPages = typeof value === "function" ? value(prevPages) : value;
return {
...prevValue,
pages: nextPages
};
});
forceUpdate((n) => n + 1);
return Promise.resolve();
}
queryClient.setQueryData(pagesQueryKey, (prevValue = {
data: [],
total_count: 0
}) => {
return typeof value === "function" ? value(prevValue) : value;
});
forceUpdate((n) => n + 1);
return Promise.resolve();
};
const revalidate = async () => {
await queryClient.invalidateQueries({ queryKey: keys.invalidationKey });
const [stablePrefix, ...rest] = keys.invalidationKey;
return queryClient.invalidateQueries({ queryKey: [stablePrefix + "-inf", ...rest] });
};
return {
data,
count,
error,
isLoading,
isFetching,
isError,
page,
pageCount,
fetchPage,
fetchNext,
fetchPrevious,
hasNextPage,
hasPreviousPage,
revalidate,
setData
};
};
//#endregion
//#region src/react/hooks/useAPIKeys.tsx
/**
* The `useAPIKeys()` hook provides access to paginated API keys for the current user or organization.
*
* @example
* ### Basic usage with default pagination
*
* ```tsx
* const { data, isLoading, page, pageCount, fetchNext, fetchPrevious } = useAPIKeys({
* subject: 'user_123',
* pageSize: 10,
* initialPage: 1,
* });
* ```
*
* @example
* ### With search query
*
* ```tsx
* const [searchValue, setSearchValue] = useState('');
* const debouncedSearch = useDebounce(searchValue, 500);
*
* const { data, isLoading } = useAPIKeys({
* subject: 'user_123',
* query: debouncedSearch.trim(),
* pageSize: 10,
* });
* ```
*
* @example
* ### Infinite scroll
*
* ```tsx
* const { data, isLoading, fetchNext, hasNextPage } = useAPIKeys({
* subject: 'user_123',
* infinite: true,
* });
* ```
*/
function useAPIKeys(params) {
useAssertWrappedByClerkProvider("useAPIKeys");
const safeValues = useWithSafeValues(params, {
initialPage: 1,
pageSize: 10,
keepPreviousData: false,
infinite: false,
subject: "",
query: "",
enabled: true
});
const clerk = useClerkInstanceContext();
clerk.telemetry?.record(eventMethodCalled("useAPIKeys"));
const hookParams = {
initialPage: safeValues.initialPage,
pageSize: safeValues.pageSize,
...safeValues.subject ? { subject: safeValues.subject } : {},
...safeValues.query ? { query: safeValues.query } : {}
};
const isEnabled = (safeValues.enabled ?? true) && clerk.loaded;
return usePagesOrInfinite({
fetcher: clerk.apiKeys?.getAll ? (params) => clerk.apiKeys.getAll({
...params,
subject: safeValues.subject
}) : void 0,
config: {
keepPreviousData: safeValues.keepPreviousData,
infinite: safeValues.infinite,
enabled: isEnabled,
isSignedIn: clerk.user !== null,
initialPage: safeValues.initialPage,
pageSize: safeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.API_KEYS_KEY,
authenticated: true,
tracked: { subject: safeValues.subject },
untracked: { args: hookParams }
})
});
}
//#endregion
//#region src/react/hooks/base/useUserBase.ts
function useUserBase() {
const clerk = useClerkInstanceContext();
const initialState = useInitialStateContext();
const getInitialState = useCallback(() => initialState?.user, [initialState?.user]);
return useSyncExternalStore(useCallback((callback) => {
return clerk.addListener(callback, { skipInitialEmit: true });
}, [clerk]), useCallback(() => {
if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return getInitialState();
return clerk.__internal_lastEmittedResources.user;
}, [clerk, getInitialState]), getInitialState);
}
//#endregion
//#region src/react/hooks/useOAuthConsent.shared.ts
function useOAuthConsentCacheKeys(params) {
const { userId, oauthClientId, scope, redirectUri } = params;
return useMemo(() => {
const args = {
oauthClientId,
...scope !== void 0 && { scope },
...redirectUri !== void 0 && { redirectUri }
};
return createCacheKeys({
stablePrefix: STABLE_KEYS.OAUTH_CONSENT_INFO_KEY,
authenticated: true,
tracked: { userId: userId ?? null },
untracked: { args }
});
}, [
userId,
oauthClientId,
scope,
redirectUri
]);
}
//#endregion
//#region src/react/hooks/useOAuthConsent.tsx
const HOOK_NAME$4 = "useOAuthConsent";
/**
* The `useOAuthConsent()` hook loads OAuth application consent metadata for the **signed-in** user
* (`GET /me/oauth/consent/{oauthClientId}`). Ensure the user is authenticated before relying on this hook
* (e.g., redirect to sign-in on your custom consent route).
*
* @example
* ```tsx
* import { useOAuthConsent } from '@clerk/react/internal'
*
* const { data, isLoading, error } = useOAuthConsent({
* oauthClientId: clientIdFromProps,
* scope: scopeFromProps,
* })
* ```
*/
function useOAuthConsent(params) {
useAssertWrappedByClerkProvider(HOOK_NAME$4);
const { oauthClientId: oauthClientIdParam, scope, redirectUri, keepPreviousData = true, enabled = true } = params;
const clerk = useClerkInstanceContext();
const user = useUserBase();
const oauthClientId = (oauthClientIdParam ?? "").trim();
clerk.telemetry?.record(eventMethodCalled(HOOK_NAME$4));
const { queryKey } = useOAuthConsentCacheKeys({
userId: user?.id ?? null,
oauthClientId,
scope,
redirectUri
});
const hasClientId = oauthClientId.length > 0;
const queryEnabled = Boolean(user) && hasClientId && enabled && clerk.loaded;
const query = useClerkQuery({
queryKey,
queryFn: () => fetchConsentInfo(clerk, {
oauthClientId,
scope,
redirectUri
}),
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData && queryEnabled)
});
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching
};
}
function fetchConsentInfo(clerk, params) {
return clerk.oauthApplication.getConsentInfo(params);
}
//#endregion
//#region src/react/hooks/base/useOrganizationBase.ts
function useOrganizationBase() {
const clerk = useClerkInstanceContext();
const initialState = useInitialStateContext();
const getInitialState = useCallback(() => initialState?.organization, [initialState?.organization]);
return useSyncExternalStore(useCallback((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), useCallback(() => {
if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return getInitialState();
return clerk.__internal_lastEmittedResources.organization;
}, [clerk, getInitialState]), getInitialState);
}
//#endregion
//#region src/react/hooks/base/useSessionBase.ts
function useSessionBase() {
const clerk = useClerkInstanceContext();
const initialState = useInitialStateContext();
const getInitialState = useCallback(() => {
return initialState ? deriveFromSsrInitialState(initialState)?.session : void 0;
}, [initialState]);
return useSyncExternalStore(useCallback((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), useCallback(() => {
if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return getInitialState();
return clerk.__internal_lastEmittedResources.session;
}, [clerk, getInitialState]), getInitialState);
}
//#endregion
//#region src/react/hooks/useClerk.ts
/**
* > [!WARNING]
* > This hook should only be used for advanced use cases, such as building a completely custom OAuth flow or as an escape hatch to access to the `Clerk` object.
*
* The `useClerk()` hook provides access to the [`Clerk`](https://clerk.com/docs/reference/objects/clerk) object, allowing you to build alternatives to any Clerk Component.
*
* @function
*
* @returns The `useClerk()` hook returns the `Clerk` object, which includes all the methods and properties listed in the [`Clerk` reference](https://clerk.com/docs/reference/objects/clerk).
*
* @example
*
* The following example uses the `useClerk()` hook to access the `clerk` object. The `clerk` object is used to call the [`openSignIn()`](https://clerk.com/docs/reference/objects/clerk#sign-in) method to open the sign-in modal.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/Home.tsx' }}
* import { useClerk } from '@clerk/react'
*
* export default function Home() {
* const clerk = useClerk()
*
* return <button onClick={() => clerk.openSignIn({})}>Sign in</button>
* }
* ```
*
* </Tab>
* <Tab>
*
* ```tsx {{ filename: 'app/page.tsx' }}
* 'use client';
*
* import { useClerk } from '@clerk/nextjs';
*
* export default function HomePage() {
* const clerk = useClerk();
*
* return <button onClick={() => clerk.openSignIn({})}>Sign in</button>;
* }
* ```
*
* </Tab>
* </Tabs>
*/
const useClerk = () => {
useAssertWrappedByClerkProvider("useClerk");
return useClerkInstanceContext();
};
//#endregion
//#region src/react/hooks/useAttemptToEnableOrganizations.ts
/**
* Attempts to enable the organizations environment setting for a given caller
*
* @internal
*/
function useAttemptToEnableOrganizations(caller) {
const clerk = useClerk();
const hasAttempted = useRef(false);
useEffect(() => {
if (hasAttempted.current) return;
hasAttempted.current = true;
clerk.__internal_attemptToEnableEnvironmentSetting?.({
for: "organizations",
caller
});
}, [clerk, caller]);
}
//#endregion
//#region src/react/hooks/useOrganization.tsx
const undefinedPaginatedResource$1 = {
data: void 0,
count: void 0,
error: void 0,
isLoading: false,
isFetching: false,
isError: false,
page: void 0,
pageCount: void 0,
fetchPage: void 0,
fetchNext: void 0,
fetchPrevious: void 0,
hasNextPage: false,
hasPreviousPage: false,
revalidate: void 0,
setData: void 0
};
/**
* The `useOrganization()` hook retrieves attributes of the currently Active Organization.
*
* @example
* ### Expand and paginate attributes
*
* To keep network usage to a minimum, developers are required to opt-in by specifying which resource they need to fetch and paginate through. By default, the `memberships`, `invitations`, `membershipRequests`, and `domains` attributes are not populated. You must pass `true` or an object with the desired properties to fetch and paginate the data.
*
* ```tsx
* // invitations.data will never be populated.
* const { invitations } = useOrganization()
*
* // Use default values to fetch invitations, such as initialPage = 1 and pageSize = 10
* const { invitations } = useOrganization({
* invitations: true,
* })
*
* // Pass your own values to fetch invitations
* const { invitations } = useOrganization({
* invitations: {
* pageSize: 20,
* initialPage: 2, // skips the first page
* },
* })
*
* // Aggregate pages in order to render an infinite list
* const { invitations } = useOrganization({
* invitations: {
* infinite: true,
* },
* })
* ```
*
* @example
* ### Infinite pagination
*
* The following example demonstrates how to use the `infinite` property to fetch and append new data to the existing list. The `memberships` attribute will be populated with the first page of the Organization's memberships. When the "Load more" button is clicked, the `fetchNext` helper function will be called to append the next page of memberships to the list.
*
* ```tsx
* import { useOrganization } from '@clerk/react'
*
* export default function MemberList() {
* const { memberships } = useOrganization({
* memberships: {
* infinite: true, // Append new data to the existing list
* keepPreviousData: true, // Persist the cached data until the new data has been fetched
* },
* })
*
* if (!memberships) {
* // Handle loading state
* return null
* }
*
* return (
* <div>
* <h2>Organization members</h2>
* <ul>
* {memberships.data?.map((membership) => (
* <li key={membership.id}>
* {membership.publicUserData.firstName} {membership.publicUserData.lastName} <
* {membership.publicUserData.identifier}> :: {membership.role}
* </li>
* ))}
* </ul>
*
* <button
* disabled={!memberships.hasNextPage} // Disable the button if there are no more available pages to be fetched
* onClick={memberships.fetchNext}
* >
* Load more
* </button>
* </div>
* )
* }
* ```
*
* @example
* ### Simple pagination
*
* The following example demonstrates how to use the `fetchPrevious` and `fetchNext` helper functions to paginate through the data. The `memberships` attribute will be populated with the first page of the Organization's memberships. When the "Previous page" or "Next page" button is clicked, the `fetchPrevious` or `fetchNext` helper function will be called to fetch the previous or next page of memberships.
*
* Notice the difference between this example's pagination and the infinite pagination example above.
*
* ```tsx
* import { useOrganization } from '@clerk/react'
*
* export default function MemberList() {
* const { memberships } = useOrganization({
* memberships: {
* keepPreviousData: true, // Persist the cached data until the new data has been fetched
* },
* })
*
* if (!memberships) {
* // Handle loading state
* return null
* }
*
* return (
* <div>
* <h2>Organization members</h2>
* <ul>
* {memberships.data?.map((membership) => (
* <li key={membership.id}>
* {membership.publicUserData.firstName} {membership.publicUserData.lastName} <
* {membership.publicUserData.identifier}> :: {membership.role}
* </li>
* ))}
* </ul>
*
* <button disabled={!memberships.hasPreviousPage} onClick={memberships.fetchPrevious}>
* Previous page
* </button>
*
* <button disabled={!memberships.hasNextPage} onClick={memberships.fetchNext}>
* Next page
* </button>
* </div>
* )
* }
* ```
*/
function useOrganization(params) {
const { domains: domainListParams, membershipRequests: membershipRequestsListParams, memberships: membersListParams, invitations: invitationsListParams } = params || {};
useAssertWrappedByClerkProvider("useOrganization");
useAttemptToEnableOrganizations("useOrganization");
const organization = useOrganizationBase();
const session = useSessionBase();
const domainSafeValues = useWithSafeValues(domainListParams, {
initialPage: 1,
pageSize: 10,
keepPreviousData: false,
infinite: false,
enrollmentMode: void 0
});
const membershipRequestSafeValues = useWithSafeValues(membershipRequestsListParams, {
initialPage: 1,
pageSize: 10,
status: "pending",
keepPreviousData: false,
infinite: false
});
const membersSafeValues = useWithSafeValues(membersListParams, {
initialPage: 1,
pageSize: 10,
role: void 0,
keepPreviousData: false,
infinite: false,
query: void 0
});
const invitationsSafeValues = useWithSafeValues(invitationsListParams, {
initialPage: 1,
pageSize: 10,
status: ["pending"],
keepPreviousData: false,
infinite: false
});
const clerk = useClerkInstanceContext();
clerk.telemetry?.record(eventMethodCalled("useOrganization"));
const domainParams = typeof domainListParams === "undefined" ? void 0 : {
initialPage: domainSafeValues.initialPage,
pageSize: domainSafeValues.pageSize,
enrollmentMode: domainSafeValues.enrollmentMode
};
const membershipRequestParams = typeof membershipRequestsListParams === "undefined" ? void 0 : {
initialPage: membershipRequestSafeValues.initialPage,
pageSize: membershipRequestSafeValues.pageSize,
status: membershipRequestSafeValues.status
};
const membersParams = typeof membersListParams === "undefined" ? void 0 : {
initialPage: membersSafeValues.initialPage,
pageSize: membersSafeValues.pageSize,
role: membersSafeValues.role,
query: membersSafeValues.query
};
const invitationsParams = typeof invitationsListParams === "undefined" ? void 0 : {
initialPage: invitationsSafeValues.initialPage,
pageSize: invitationsSafeValues.pageSize,
status: invitationsSafeValues.status
};
const domains = usePagesOrInfinite({
fetcher: organization?.getDomains,
config: {
keepPreviousData: domainSafeValues.keepPreviousData,
infinite: domainSafeValues.infinite,
enabled: !!domainParams,
isSignedIn: organization !== null,
initialPage: domainSafeValues.initialPage,
pageSize: domainSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.DOMAINS_KEY,
authenticated: true,
tracked: { organizationId: organization?.id },
untracked: { args: domainParams }
})
});
const membershipRequests = usePagesOrInfinite({
fetcher: organization?.getMembershipRequests,
config: {
keepPreviousData: membershipRequestSafeValues.keepPreviousData,
infinite: membershipRequestSafeValues.infinite,
enabled: !!membershipRequestParams,
isSignedIn: organization !== null,
initialPage: membershipRequestSafeValues.initialPage,
pageSize: membershipRequestSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.MEMBERSHIP_REQUESTS_KEY,
authenticated: true,
tracked: { organizationId: organization?.id },
untracked: { args: membershipRequestParams }
})
});
const memberships = usePagesOrInfinite({
fetcher: organization?.getMemberships,
config: {
keepPreviousData: membersSafeValues.keepPreviousData,
infinite: membersSafeValues.infinite,
enabled: !!membersParams,
isSignedIn: organization !== null,
initialPage: membersSafeValues.initialPage,
pageSize: membersSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.MEMBERSHIPS_KEY,
authenticated: true,
tracked: { organizationId: organization?.id },
untracked: { args: membersParams }
})
});
const invitations = usePagesOrInfinite({
fetcher: organization?.getInvitations,
config: {
keepPreviousData: invitationsSafeValues.keepPreviousData,
infinite: invitationsSafeValues.infinite,
enabled: !!invitationsParams,
isSignedIn: organization !== null,
initialPage: invitationsSafeValues.initialPage,
pageSize: invitationsSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.INVITATIONS_KEY,
authenticated: true,
tracked: { organizationId: organization?.id },
untracked: { args: invitationsParams }
})
});
if (organization === void 0) return {
isLoaded: false,
organization: void 0,
membership: void 0,
domains: undefinedPaginatedResource$1,
membershipRequests: undefinedPaginatedResource$1,
memberships: undefinedPaginatedResource$1,
invitations: undefinedPaginatedResource$1
};
if (organization === null) return {
isLoaded: true,
organization: null,
membership: null,
domains: null,
membershipRequests: null,
memberships: null,
invitations: null
};
/** In SSR context we include only the organization object when loadOrg is set to true. */
if (!clerk.loaded && organization) return {
isLoaded: true,
organization,
membership: void 0,
domains: undefinedPaginatedResource$1,
membershipRequests: undefinedPaginatedResource$1,
memberships: undefinedPaginatedResource$1,
invitations: undefinedPaginatedResource$1
};
return {
isLoaded: clerk.loaded,
organization,
membership: getCurrentOrganizationMembership(session.user.organizationMemberships, organization.id),
domains,
membershipRequests,
memberships,
invitations
};
}
//#endregion
//#region src/react/hooks/useOrganizationCreationDefaults.shared.ts
function useOrganizationCreationDefaultsCacheKeys(params) {
const { userId } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: STABLE_KEYS.ORGANIZATION_CREATION_DEFAULTS_KEY,
authenticated: Boolean(userId),
tracked: { userId: userId ?? null },
untracked: { args: {} }
});
}, [userId]);
}
//#endregion
//#region src/react/hooks/useOrganizationCreationDefaults.tsx
const HOOK_NAME$3 = "useOrganizationCreationDefaults";
/**
* The `useOrganizationCreationDefaults()` hook retrieves the organization creation defaults for the current user.
*
* @example
* ### Basic usage
*
* ```tsx
* import { useOrganizationCreationDefaults } from '@clerk/clerk-react'
*
* export default function CreateOrganizationForm() {
* const { data, isLoading } = useOrganizationCreationDefaults()
*
* if (isLoading) return <div>Loading...</div>
*
* return (
* <form>
* <input defaultValue={data?.form.name} placeholder="Organization name" />
* <input defaultValue={data?.form.slug} placeholder="Slug" />
* <button type="submit">Create</button>
* </form>
* )
* }
* ```
*/
function useOrganizationCreationDefaults(params = {}) {
useAssertWrappedByClerkProvider(HOOK_NAME$3);
const { keepPreviousData = true, enabled = true } = params;
const clerk = useClerkInstanceContext();
const user = useUserBase();
const featureEnabled = clerk.__internal_environment?.organizationSettings?.organizationCreationDefaults?.enabled ?? false;
clerk.telemetry?.record(eventMethodCalled(HOOK_NAME$3));
const { queryKey } = useOrganizationCreationDefaultsCacheKeys({ userId: user?.id ?? null });
const queryEnabled = Boolean(user) && enabled && featureEnabled && clerk.loaded;
const query = useClerkQuery({
queryKey,
queryFn: user?.getOrganizationCreationDefaults,
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData)
});
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching
};
}
//#endregion
//#region src/react/hooks/useOrganizationList.tsx
const undefinedPaginatedResource = {
data: void 0,
count: void 0,
error: void 0,
isLoading: false,
isFetching: false,
isError: false,
page: void 0,
pageCount: void 0,
fetchPage: void 0,
fetchNext: void 0,
fetchPrevious: void 0,
hasNextPage: false,
hasPreviousPage: false,
revalidate: void 0,
setData: void 0
};
/**
* The `useOrganizationList()` hook provides access to the current user's organization memberships, invitations, and suggestions. It also includes methods for creating new organizations and managing the active organization.
*
* @example
* ### Expanding and paginating attributes
*
* To keep network usage to a minimum, developers are required to opt-in by specifying which resource they need to fetch and paginate through. So by default, the `userMemberships`, `userInvitations`, and `userSuggestions` attributes are not populated. You must pass true or an object with the desired properties to fetch and paginate the data.
*
* ```tsx
* // userMemberships.data will never be populated
* const { userMemberships } = useOrganizationList()
*
* // Use default values to fetch userMemberships, such as initialPage = 1 and pageSize = 10
* const { userMemberships } = useOrganizationList({
* userMemberships: true,
* })
*
* // Pass your own values to fetch userMemberships
* const { userMemberships } = useOrganizationList({
* userMemberships: {
* pageSize: 20,
* initialPage: 2, // skips the first page
* },
* })
*
* // Aggregate pages in order to render an infinite list
* const { userMemberships } = useOrganizationList({
* userMemberships: {
* infinite: true,
* },
* })
* ```
*
* @example
* ### Infinite pagination
*
* The following example demonstrates how to use the `infinite` property to fetch and append new data to the existing list. The `userMemberships` attribute will be populated with the first page of the user's Organization memberships. When the "Load more" button is clicked, the `fetchNext` helper function will be called to append the next page of memberships to the list.
*
* ```tsx {{ filename: 'src/components/JoinedOrganizations.tsx' }}
* import { useOrganizationList } from '@clerk/react'
* import React from 'react'
*
* const JoinedOrganizations = () => {
* const { isLoaded, setActive, userMemberships } = useOrganizationList({
* userMemberships: {
* infinite: true,
* },
* })
*
* if (!isLoaded) {
* return <>Loading</>
* }
*
* return (
* <>
* <ul>
* {userMemberships.data?.map((mem) => (
* <li key={mem.id}>
* <span>{mem.organization.name}</span>
* <button onClick={() => setActive({ organization: mem.organization.id })}>Select</button>
* </li>
* ))}
* </ul>
*
* <button disabled={!userMemberships.hasNextPage} onClick={() => userMemberships.fetchNext()}>
* Load more
* </button>
* </>
* )
* }
*
* export default JoinedOrganizations
* ```
*
* @example
* ### Simple pagination
*
* The following example demonstrates how to use the `fetchPrevious` and `fetchNext` helper functions to paginate through the data. The `userInvitations` attribute will be populated with the first page of invitations. When the "Previous page" or "Next page" button is clicked, the `fetchPrevious` or `fetchNext` helper function will be called to fetch the previous or next page of invitations.
*
* Notice the difference between this example's pagination and the infinite pagination example above.
*
* ```tsx {{ filename: 'src/components/UserInvitationsTable.tsx' }}
* import { useOrganizationList } from '@clerk/react'
* import React from 'react'
*
* const UserInvitationsTable = () => {
* const { isLoaded, userInvitations } = useOrganizationList({
* userInvitations: {
* infinite: true,
* keepPreviousData: true,
* },
* })
*
* if (!isLoaded || userInvitations.isLoading) {
* return <>Loading</>
* }
*
* return (
* <>
* <table>
* <thead>
* <tr>
* <th>Email</th>
* <th>Org name</th>
* </tr>
* </thead>
*
* <tbody>
* {userInvitations.data?.map((inv) => (
* <tr key={inv.id}>
* <th>{inv.emailAddress}</th>
* <th>{inv.publicOrganizationData.name}</th>
* </tr>
* ))}
* </tbody>
* </table>
*
* <button disabled={!userInvitations.hasPreviousPage} onClick={userInvitations.fetchPrevious}>
* Prev
* </button>
* <button disabled={!userInvitations.hasNextPage} onClick={userInvitations.fetchNext}>
* Next
* </button>
* </>
* )
* }
*
* export default UserInvitationsTable
* ```
*/
function useOrganizationList(params) {
const { userMemberships, userInvitations, userSuggestions } = params || {};
useAssertWrappedByClerkProvider("useOrganizationList");
useAttemptToEnableOrganizations("useOrganizationList");
const userMembershipsSafeValues = useWithSafeValues(userMemberships, {
initialPage: 1,
pageSize: 10,
keepPreviousData: false,
infinite: false
});
const userInvitationsSafeValues = useWithSafeValues(userInvitations, {
initialPage: 1,
pageSize: 10,
status: "pending",
keepPreviousData: false,
infinite: false
});
const userSuggestionsSafeValues = useWithSafeValues(userSuggestions, {
initialPage: 1,
pageSize: 10,
status: "pending",
keepPreviousData: false,
infinite: false
});
const clerk = useClerkInstanceContext();
const user = useUserBase();
clerk.telemetry?.record(eventMethodCalled("useOrganizationList"));
const userMembershipsParams = typeof userMemberships === "undefined" ? void 0 : {
initialPage: userMembershipsSafeValues.initialPage,
pageSize: userMembershipsSafeValues.pageSize
};
const userInvitationsParams = typeof userInvitations === "undefined" ? void 0 : {
initialPage: userInvitationsSafeValues.initialPage,
pageSize: userInvitationsSafeValues.pageSize,
status: userInvitationsSafeValues.status
};
const userSuggestionsParams = typeof userSuggestions === "undefined" ? void 0 : {
initialPage: userSuggestionsSafeValues.initialPage,
pageSize: userSuggestionsSafeValues.pageSize,
status: userSuggestionsSafeValues.status
};
const isClerkLoaded = !!(clerk.loaded && user);
const memberships = usePagesOrInfinite({
fetcher: user?.getOrganizationMemberships,
config: {
keepPreviousData: userMembershipsSafeValues.keepPreviousData,
infinite: userMembershipsSafeValues.infinite,
enabled: !!userMembershipsParams,
isSignedIn: user !== null,
initialPage: userMembershipsSafeValues.initialPage,
pageSize: userMembershipsSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.USER_MEMBERSHIPS_KEY,
authenticated: true,
tracked: { userId: user?.id },
untracked: { args: userMembershipsParams }
})
});
const invitations = usePagesOrInfinite({
fetcher: user?.getOrganizationInvitations,
config: {
keepPreviousData: userInvitationsSafeValues.keepPreviousData,
infinite: userInvitationsSafeValues.infinite,
enabled: !!userInvitationsParams,
isSignedIn: user !== null,
initialPage: userInvitationsSafeValues.initialPage,
pageSize: userInvitationsSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.USER_INVITATIONS_KEY,
authenticated: true,
tracked: { userId: user?.id },
untracked: { args: userInvitationsParams }
})
});
const suggestions = usePagesOrInfinite({
fetcher: user?.getOrganizationSuggestions,
config: {
keepPreviousData: userSuggestionsSafeValues.keepPreviousData,
infinite: userSuggestionsSafeValues.infinite,
enabled: !!userSuggestionsParams,
isSignedIn: user !== null,
initialPage: userSuggestionsSafeValues.initialPage,
pageSize: userSuggestionsSafeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: STABLE_KEYS.USER_SUGGESTIONS_KEY,
authenticated: true,
tracked: { userId: user?.id },
untracked: { args: userSuggestionsParams }
})
});
if (!isClerkLoaded) return {
isLoaded: false,
createOrganization: void 0,
setActive: void 0,
userMemberships: undefinedPaginatedResource,
userInvitations: undefinedPaginatedResource,
userSuggestions: undefinedPaginatedResource
};
return {
isLoaded: isClerkLoaded,
setActive: clerk.setActive,
createOrganization: clerk.createOrganization,
userMemberships: memberships,
userInvitations: invitations,
userSuggestions: suggestions
};
}
//#endregion
//#region src/react/hooks/useSafeLayoutEffect.tsx
/**
* @internal
*/
const useSafeLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
//#endregion
//#region src/react/hooks/useSession.ts
const hookName$2 = `useSession`;
/**
* The `useSession()` hook provides access to the current user's [`Session`](https://clerk.com/docs/reference/objects/session) object, as well as helpers for setting the active session.
*
* @unionReturnHeadings
* ["Loading", "Signed out", "Signed in"]
*
* @function
*
* @param [options] - An object containing options for the `useSession()` hook.
* @example
* ### Access the `Session` object
*
* The following example uses the `useSession()` hook to access the `Session` object, which has the `lastActiveAt` property. The `lastActiveAt` property is a `Date` object used to show the time the session was last active.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/Home.tsx' }}
* import { useSession } from '@clerk/react'
*
* export default function Home() {
* const { isLoaded, session, isSignedIn } = useSession()
*
* if (!isLoaded) {
* // Handle loading state
* return null
* }
* if (!isSignedIn) {
* // Handle signed out state
* return null
* }
*
* return (
* <div>
* <p>This session has been active since {session.lastActiveAt.toLocaleString()}</p>
* </div>
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* ```tsx {{ filename: 'app/page.tsx' }}
* 'use client';
*
* import { useSession } from '@clerk/nextjs';
*
* export default function HomePage() {
* const { isLoaded, session, isSignedIn } = useSession();
*
* if (!isLoaded) {
* // Handle loading state
* return null;
* }
* if (!isSignedIn) {
* // Handle signed out state
* return null;
* }
*
* return (
* <div>
* <p>This session has been active since {session.lastActiveAt.toLocaleString()}</p>
* </div>
* );
* }
* ```
*
* </Tab>
* </Tabs>
*/
const useSession = () => {
useAssertWrappedByClerkProvider(hookName$2);
const session = useSessionBase();
const clerk = useClerkInstanceContext();
clerk.telemetry?.record(eventMethodCalled(hookName$2));
if (session === void 0) return {
isLoaded: false,
isSignedIn: void 0,
session: void 0
};
if (session === null) return {
isLoaded: true,
isSignedIn: false,
session: null
};
return {
isLoaded: true,
isSignedIn: clerk.isSignedIn,
session
};
};
//#endregion
//#region src/react/hooks/base/useClientBase.ts
const initialSnapshot = void 0;
const getInitialSnapshot = () => initialSnapshot;
function useClientBase() {
const clerk = useClerkInstanceContext();
return useSyncExternalStore(useCallback((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), useCallback(() => {
if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return;
return clerk.__internal_lastEmittedResources.client;
}, [clerk]), getInitialSnapshot);
}
//#endregion
//#region src/react/hooks/useSessionList.ts
const hookName$1 = "useSessionList";
/**
* The `useSessionList()` hook returns an array of [`Session`](https://clerk.com/docs/reference/objects/session) objects that have been registered on the client device.
*
* @unionReturnHeadings
* ["Initialization", "Loaded"]
*
* @function
*
* @example
* ### Get a list of sessions
*
* The following example uses `useSessionList()` to get a list of sessions that have been registered on the client device. The `sessions` property is used to show the number of times the user has visited the page.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/Home.tsx' }}
* import { useSessionList } from '@clerk/react'
*
* export default function Home() {
* const { isLoaded, sessions } = useSessionList()
*
* if (!isLoaded) {
* // Handle loading state
* return null
* }
*
* return (
* <div>
* <p>Welcome back. You've been here {sessions.length} times before.</p>
* </div>
* )
* }
* ```
*
* </Tab>
* <Tab>
*
* ```tsx {{ filename: 'app/page.tsx' }}
* 'use client';
*
* import { useSessionList } from '@clerk/nextjs';
*
* export default function HomePage() {
* const { isLoaded, sessions } = useSessionList();
*
* if (!isLoaded) {
* // Handle loading state
* return null;
* }
*
* return (
* <div>
* <p>Welcome back. You've been here {sessions.length} times before.</p>
* </div>
* );
* }
* ```
*
* </Tab>
* </Tabs>
*/
const useSessionList = () => {
useAssertWrappedByClerkProvider(hookName$1);
const isomorphicClerk = useClerkInstanceContext();
const client = useClientBase();
useClerkInstanceContext().telemetry?.record(eventMethodCalled(hookName$1));
if (!client) return {
isLoaded: false,
sessions: void 0,
setActive: void 0
};
return {
isLoaded: true,
sessions: client.sessions,
setActive: isomorphicClerk.setActive
};
};
//#endregion
//#region src/react/hooks/useUser.ts
const hookName = "useUser";
/**
* The `useUser()` hook provides access to the current user's [`User`](https://clerk.com/docs/reference/objects/user) object, which contains all the data for a single user in your application and provides methods to manage their account. This hook also allows you to check if the user is signed in and if Clerk has loaded.
*
* @unionReturnHeadings
* ["Loading", "Signed out", "Signed in"]
*
* @example
* ### Get the current user
*
* The following example uses the `useUser()` hook to access the [`User`](https://clerk.com/docs/reference/objects/user) object, which contains the current user's data such as their full name. The `isLoaded` and `isSignedIn` properties are used to handle the loading state and to check if the user is signed in, respectively.
*
* ```tsx {{ filename: 'src/Example.tsx' }}
* import { useUser } from '@clerk/react'
*
* export default function Example() {
* const { isSignedIn, user, isLoaded } = useUser()
*
* if (!isLoaded) {
* return <div>Loading...</div>
* }
*
* if (!isSignedIn) {
* return <div>Sign in to view this page</div>
* }
*
* return <div>Hello {user.firstName}!</div>
* }
* ```
*
* @example
* ### Update user data
*
* The following example uses the `useUser()` hook to access the [`User`](https://clerk.com/docs/reference/objects/user) object, which calls the [`update()`](https://clerk.com/docs/reference/objects/user#update) method to update the current user's information.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/Home.tsx' }}
* import { useUser } from '@clerk/react'
*
* export default function Home() {
* const { isSignedIn, isLoaded, user } = useUser()
*
* if (!isLoaded) {
* // Handle loading state
* return null
* }
*
* if (!isSignedIn) return null
*
* const updateUser = async () => {
* await user.update({
* firstName: 'John',
* lastName: 'Doe',
* })
* }
*
* return (
* <>
* <button onClick={updateUser}>Update your name</button>
* <p>user.firstName: {user.firstName}</p>
* <p>user.lastName: {user.lastName}</p>
* </>
* )
* }
* ```
* </Tab>
* <Tab>
*
* ```tsx {{ filename: 'app/page.tsx' }}
* 'use client';
*
* import { useUser } from '@clerk/nextjs';
*
* export default function HomePage() {
* const { isSignedIn, isLoaded, user } = useUser();
*
* if (!isLoaded) {
* // Handle loading state
* return null;
* }
*
* if (!isSignedIn) return null;
*
* const updateUser = async () => {
* await user.update({
* firstName: 'John',
* lastName: 'Doe',
* });
* };
*
* return (
* <>
* <button onClick={updateUser}>Update your name</button>
* <p>user.firstName: {user.firstName}</p>
* <p>user.lastName: {user.lastName}</p>
* </>
* );
* }
* ```
*
* </Tab>
* </Tabs>
*
* @example
* ### Reload user data
*
* The following example uses the `useUser()` hook to access the [`User`](https://clerk.com/docs/reference/objects/user) object, which calls the [`reload()`](https://clerk.com/docs/reference/objects/user#reload) method to get the latest user's information.
*
* <Tabs items='React,Next.js'>
* <Tab>
*
* ```tsx {{ filename: 'src/Home.tsx' }}
* import { useUser } from '@clerk/react'
*
* export default function Home() {
* const { isSignedIn, isLoaded, user } = useUser();
*
* if (!isLoaded) {
* // Handle loading state
* return null;
* }
*
* if (!isSignedIn) return null;
*
* const updateUser = async () => {
* // Update data via an API endpoint
* const updateMetadata = await fetch('/api/updateMetadata', {
* method: 'POST',
* body: JSON.stringify({
* role: 'admin'
* })
* });
*
* // Check if the update was successful
* if ((await updateMetadata.json()).message !== 'success') {
* throw new Error('Error updating');
* }
*
* // If the update was successful, reload the user data
* await user.reload();
* };
*
* return (
* <>
* <button onClick={updateUser}>Update your metadata</button>
* <p>user role: {user.publicMetadata.role}</p>
* </>
* );
* }
* ```
*
* </Tab>
* <Tab>
*
* ```tsx {{ filename: 'app/page.tsx' }}
* 'use client';
*
* import { useUser } from '@clerk/nextjs';
*
* export default function HomePage() {
* const { isSignedIn, isLoaded, user } = useUser();
*
* if (!isLoaded) {
* // Handle loading state
* return null;
* }
*
* if (!isSignedIn) return null;
*
* const updateUser = async () => {
* // Update data via an API endpoint
* const updateMetadata = await fetch('/api/updateMetadata', {
* method: 'POST',
* body: JSON.stringify({
* role: 'admin',
* }),
* });
*
* // Check if the update was successful
* if ((await updateMetadata.json()).message !== 'success') {
* throw new Error('Error updating');
* }
*
* // If the update was successful, reload the user data
* await user.reload();
* };
*
* return (
* <>
* <button onClick={updateUser}>Update your metadata</button>
* <p>user role: {user.publicMetadata.role}</p>
* </>
* );
* }
* ```
*
* </Tab>
* </Tabs>
*/
function useUser() {
useAssertWrappedByClerkProvider(hookName);
const user = useUserBase();
useClerkInstanceContext().telemetry?.record(eventMethodCalled(hookName));
if (user === void 0) return {
isLoaded: false,
isSignedIn: void 0,
user: void 0
};
if (user === null) return {
isLoaded: true,
isSignedIn: false,
user: null
};
return {
isLoaded: true,
isSignedIn: true,
user
};
}
//#endregion
//#region src/react/hooks/useDeepEqualMemo.ts
const useDeepEqualMemoize = (value) => {
const ref = React.useRef(value);
if (!dequal(value, ref.current)) ref.current = value;
return React.useMemo(() => ref.current, [ref.current]);
};
/**
* @internal
*/
const useDeepEqualMemo = (factory, dependencyArray) => {
return React.useMemo(factory, useDeepEqualMemoize(dependencyArray));
};
/**
* @internal
*/
const isDeeplyEqual = dequal;
//#endregion
//#region src/react/hooks/useReverification.ts
const CLERK_API_REVERIFICATION_ERROR_CODE = "session_reverification_required";
/**
*
*/
async function resolveResult(result) {
try {
const r = await result;
if (r instanceof Response) return r.json();
return r;
} catch (e) {
if (isClerkAPIResponseError(e) && e.errors.find(({ code }) => code === CLERK_API_REVERIFICATION_ERROR_CODE)) return reverificationError();
throw e;
}
}
/**
*
*/
function createReverificationHandler(params) {
/**
*
*/
function assertReverification(fetcher) {
return (async (...args) => {
let result = await resolveResult(fetcher(...args));
if (isReverificationHint(result)) {
/**
* Create a promise
*/
const resolvers = createDeferredPromise();
const isValidMetadata = validateReverificationConfig(result.clerk_error.metadata?.reverification);
const level = isValidMetadata ? isValidMetadata().level : void 0;
const cancel = () => {
resolvers.reject(new ClerkRuntimeError("User cancelled attempted verification", { code: "reverification_cancelled" }));
};
const complete = () => {
resolvers.resolve(true);
};
if (params.onNeedsReverification === void 0)
/**
* On success resolve the pending promise
* On cancel reject the pending promise
*/
params.openUIComponent?.({
level,
afterVerification: complete,
afterVerificationCancelled: cancel
});
else params.onNeedsReverification({
cancel,
complete,
level
});
/**
* Wait until the promise from above have been resolved or rejected
*/
await resolvers.promise;
/**
* After the promise resolved successfully try the original request one more time
*/
result = await resolveResult(fetcher(...args));
}
return result;
});
}
return assertReverification;
}
/**
* > [!WARNING]
* >
* > Depending on the SDK you're using, this feature requires `@clerk/nextjs@6.12.7` or later, `@clerk/react@5.25.1` or later, and `@clerk/clerk-js@5.57.1` or later.
*
* The `useReverification()` hook is used to handle a session's reverification flow. If a request requires reverification, a modal will display, prompting the user to verify their credentials. Upon successful verification, the original request will automatically retry.
*
* @function
*
* @returns The `useReverification()` hook returns an array with the "enhanced" fetcher.
*
* @example
* ### Handle cancellation of the reverification process
*
* The following example demonstrates how to handle scenarios where a user cancels the reverification flow, such as closing the modal, which might result in `myData` being `null`.
*
* In the following example, `myFetcher` would be a function in your backend that fetches data from the route that requires reverification. See the [guide on how to require reverification](https://clerk.com/docs/guides/secure/reverification) for more information.
*
* ```tsx {{ filename: 'src/components/MyButton.tsx' }}
* import { useReverification } from '@clerk/react'
* import { isReverificationCancelledError } from '@clerk/react/error'
*
* type MyData = {
* balance: number
* }
*
* export function MyButton() {
* const fetchMyData = () => fetch('/api/balance').then(res=> res.json() as Promise<MyData>)
* const enhancedFetcher = useReverification(fetchMyData);
*
* const handleClick = async () => {
* try {
* const myData = await enhancedFetcher()
* // ^ is types as `MyData`
* } catch (e) {
* // Handle error returned from the fetcher here
*
* // You can also handle cancellation with the following
* if (isReverificationCancelledError(err)) {
* // Handle the cancellation error here
* }
* }
* }
*
* return <button onClick={handleClick}>Update User</button>
* }
* ```
*/
const useReverification = (fetcher, options) => {
const { __internal_openReverification, telemetry } = useClerk();
const fetcherRef = useRef(fetcher);
const optionsRef = useRef(options);
telemetry?.record(eventMethodCalled("useReverification", { onNeedsReverification: Boolean(options?.onNeedsReverification) }));
useSafeLayoutEffect(() => {
fetcherRef.current = fetcher;
optionsRef.current = options;
});
return useCallback((...args) => {
return createReverificationHandler({
openUIComponent: __internal_openReverification,
telemetry,
...optionsRef.current
})(fetcherRef.current)(...args);
}, [__internal_openReverification, telemetry]);
};
//#endregion
//#region src/react/hooks/useBillingIsEnabled.ts
/**
* @internal
*/
function useBillingIsEnabled(params) {
const clerk = useClerkInstanceContext();
const enabledFromParam = params?.enabled ?? true;
const environment = clerk.__internal_environment;
const user = useUserBase();
const organization = useOrganizationBase();
const userBillingEnabled = environment?.commerceSettings.billing.user.enabled;
const orgBillingEnabled = environment?.commerceSettings.billing.organization.enabled;
const billingEnabled = params?.for === "organization" ? orgBillingEnabled : params?.for === "user" ? userBillingEnabled : userBillingEnabled || orgBillingEnabled;
const isOrganization = params?.for === "organization";
const requireUserAndOrganizationWhenAuthenticated = params?.authenticated ?? true ? (isOrganization ? Boolean(organization?.id) : true) && Boolean(user?.id) : true;
return billingEnabled && enabledFromParam && clerk.loaded && requireUserAndOrganizationWhenAuthenticated;
}
//#endregion
//#region src/react/hooks/createBillingPaginatedHook.tsx
/**
* A hook factory that creates paginated data fetching hooks for commerce-related resources.
* It provides a standardized way to create hooks that can fetch either user or Organization resources
* with built-in pagination support.
*
* The generated hooks handle:
* - Clerk authentication context
* - Resource-specific data fetching
* - Pagination (both traditional and infinite scroll)
* - Telemetry tracking
* - Type safety for the specific resource.
*
* @internal
*/
function createBillingPaginatedHook({ hookName, resourceType, useFetcher, options }) {
return function useBillingHook(params) {
const { for: _for, enabled: externalEnabled, ...paginationParams } = params || {};
const safeFor = _for || "user";
useAssertWrappedByClerkProvider(hookName);
const fetchFn = useFetcher(safeFor);
const safeValues = useWithSafeValues(paginationParams, {
initialPage: 1,
pageSize: 10,
keepPreviousData: false,
infinite: false,
__experimental_mode: void 0
});
const clerk = useClerkInstanceContext();
const user = useUserBase();
const organization = useOrganizationBase();
clerk.telemetry?.record(eventMethodCalled(hookName));
const isForOrganization = safeFor === "organization";
const billingEnabled = useBillingIsEnabled({
for: safeFor,
enabled: externalEnabled,
authenticated: !options?.unauthenticated
});
const hookParams = typeof paginationParams === "undefined" ? void 0 : {
initialPage: safeValues.initialPage,
pageSize: safeValues.pageSize,
...options?.unauthenticated ? {} : isForOrganization ? { orgId: organization?.id } : {}
};
const isEnabled = !!hookParams && clerk.loaded && !!billingEnabled;
return usePagesOrInfinite({
fetcher: fetchFn,
config: {
keepPreviousData: safeValues.keepPreviousData,
infinite: safeValues.infinite,
enabled: isEnabled,
...options?.unauthenticated ? {} : { isSignedIn: user !== null },
__experimental_mode: safeValues.__experimental_mode,
initialPage: safeValues.initialPage,
pageSize: safeValues.pageSize
},
keys: createCacheKeys({
stablePrefix: resourceType,
authenticated: !options?.unauthenticated,
tracked: options?.unauthenticated ? { for: safeFor } : {
userId: user?.id,
...isForOrganization ? { orgId: organization?.id } : {}
},
untracked: { args: hookParams }
})
});
};
}
//#endregion
//#region src/react/hooks/useStatements.tsx
/**
* @internal
*/
const useStatements = createBillingPaginatedHook({
hookName: "useStatements",
resourceType: STABLE_KEYS.STATEMENTS_KEY,
useFetcher: () => {
const clerk = useClerkInstanceContext();
if (clerk.loaded) return clerk.billing.getStatements;
}
});
//#endregion
//#region src/react/hooks/usePaymentAttempts.tsx
/**
* @internal
*/
const usePaymentAttempts = createBillingPaginatedHook({
hookName: "usePaymentAttempts",
resourceType: STABLE_KEYS.PAYMENT_ATTEMPTS_KEY,
useFetcher: () => {
const clerk = useClerkInstanceContext();
if (clerk.loaded) return clerk.billing.getPaymentAttempts;
}
});
//#endregion
//#region src/react/hooks/usePaymentMethods.tsx
/**
* @internal
*/
const usePaymentMethods = createBillingPaginatedHook({
hookName: "usePaymentMethods",
resourceType: STABLE_KEYS.PAYMENT_METHODS_KEY,
useFetcher: (resource) => {
const organization = useOrganizationBase();
const user = useUserBase();
if (resource === "organization") return organization?.getPaymentMethods;
return user?.getPaymentMethods;
}
});
//#endregion
//#region src/react/hooks/usePlans.tsx
/**
* @internal
*/
const usePlans = createBillingPaginatedHook({
hookName: "usePlans",
resourceType: STABLE_KEYS.PLANS_KEY,
useFetcher: (_for) => {
const clerk = useClerkInstanceContext();
if (!clerk.loaded) return;
return (params) => clerk.billing.getPlans({
...params,
for: _for
});
},
options: { unauthenticated: true }
});
//#endregion
//#region src/react/hooks/useSubscription.shared.ts
function useSubscriptionCacheKeys(params) {
const { userId, orgId, for: forType } = params;
return useMemo(() => {
const safeOrgId = forType === "organization" ? orgId : void 0;
return createCacheKeys({
stablePrefix: STABLE_KEYS.SUBSCRIPTION_KEY,
authenticated: true,
tracked: {
userId,
orgId: safeOrgId
},
untracked: { args: { orgId: safeOrgId } }
});
}, [
userId,
orgId,
forType
]);
}
//#endregion
//#region src/react/hooks/useSubscription.tsx
const HOOK_NAME$2 = "useSubscription";
/**
* @internal
*/
function useSubscription(params) {
useAssertWrappedByClerkProvider(HOOK_NAME$2);
const clerk = useClerkInstanceContext();
const user = useUserBase();
const organization = useOrganizationBase();
const billingEnabled = useBillingIsEnabled(params);
const recordedRef = useRef(false);
useEffect(() => {
if (!recordedRef.current && clerk?.telemetry) {
clerk.telemetry.record(eventMethodCalled(HOOK_NAME$2));
recordedRef.current = true;
}
}, [clerk]);
const keepPreviousData = params?.keepPreviousData ?? false;
const [queryClient] = useClerkQueryClient();
const { queryKey, invalidationKey, stableKey, authenticated } = useSubscriptionCacheKeys({
userId: user?.id,
orgId: organization?.id,
for: params?.for
});
const queriesEnabled = Boolean(user?.id && billingEnabled);
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: ({ queryKey }) => {
const obj = queryKey[3];
return clerk.billing.getSubscription(obj.args);
},
staleTime: 1e3 * 60,
enabled: queriesEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData && queriesEnabled)
});
const revalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: invalidationKey }), [queryClient, invalidationKey]);
return {
data: query.data,
error: query.error ?? void 0,
isLoading: query.isLoading,
isFetching: query.isFetching,
revalidate
};
}
//#endregion
//#region src/react/hooks/useCheckout.ts
/**
* @function
*
* @param [options] - An object containing the configuration for the checkout flow.
*
* **Required** if the hook is used without a `<CheckoutProvider />` wrapping the component tree.
*/
const useCheckout = (options) => {
const contextOptions = useCheckoutContext();
const { for: forOrganization, planId, planPeriod, seatsQuantity, priceId } = options || contextOptions;
const organization = useOrganizationBase();
const { isLoaded, user } = useUser();
const clerk = useClerkInstanceContext();
if (user === null && isLoaded) throw new Error("Clerk: Ensure that `useCheckout` is inside a component wrapped with `<Show when=\"signed-in\" />`.");
if (isLoaded && forOrganization === "organization" && organization === null) throw new Error("Clerk: Ensure your flow checks for an active organization. Retrieve `orgId` from `useAuth()` and confirm it is defined. For SSR, see: https://clerk.com/docs/reference/backend/types/auth-object#how-to-access-the-auth-object");
const signal = useCallback(() => {
return clerk.__experimental_checkout({
planId,
planPeriod,
for: forOrganization,
seatsQuantity,
priceId
});
}, [
user?.id,
organization?.id,
planId,
planPeriod,
forOrganization,
seatsQuantity,
priceId
]);
const subscribe = useCallback((callback) => {
if (!clerk.loaded) return () => {};
return clerk.__internal_state.__internal_effect(() => {
signal();
callback();
});
}, [
signal,
clerk.loaded,
clerk.__internal_state
]);
const getSnapshot = useCallback(() => {
return signal();
}, [signal]);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
};
//#endregion
//#region src/react/hooks/useCreditBalance.tsx
const HOOK_NAME$1 = "useCreditBalance";
/**
* @internal
*/
function __internal_useCreditBalanceQuery(params) {
useAssertWrappedByClerkProvider(HOOK_NAME$1);
const clerk = useClerkInstanceContext();
const user = useUserBase();
const organization = useOrganizationBase();
const billingEnabled = useBillingIsEnabled(params);
const recordedRef = useRef(false);
useEffect(() => {
if (!recordedRef.current && clerk?.telemetry) {
clerk.telemetry.record(eventMethodCalled(HOOK_NAME$1));
recordedRef.current = true;
}
}, [clerk]);
const keepPreviousData = params?.keepPreviousData ?? false;
const [queryClient] = useClerkQueryClient();
const { queryKey, invalidationKey, stableKey, authenticated } = useMemo(() => {
const safeOrgId = params?.for === "organization" ? organization?.id : void 0;
return createCacheKeys({
stablePrefix: STABLE_KEYS.CREDIT_BALANCE_KEY,
authenticated: true,
tracked: {
userId: user?.id,
orgId: safeOrgId
},
untracked: { args: { orgId: safeOrgId } }
});
}, [
user?.id,
organization?.id,
params?.for
]);
const queriesEnabled = Boolean(user?.id && billingEnabled && (params?.enabled ?? true));
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: ({ queryKey }) => {
const obj = queryKey[3];
return clerk.billing.getCreditBalance(obj.args);
},
staleTime: 1e3 * 60,
enabled: queriesEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData && queriesEnabled)
});
const revalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: invalidationKey }), [queryClient, invalidationKey]);
return {
data: query.data,
error: query.error ?? void 0,
isLoading: query.isLoading,
isFetching: query.isFetching,
revalidate
};
}
//#endregion
//#region src/react/hooks/useCreditHistory.tsx
const HOOK_NAME = "useCreditHistory";
/**
* @internal
*/
function __internal_useCreditHistoryQuery(params) {
useAssertWrappedByClerkProvider(HOOK_NAME);
const clerk = useClerkInstanceContext();
const user = useUserBase();
const organization = useOrganizationBase();
const billingEnabled = useBillingIsEnabled(params);
const recordedRef = useRef(false);
useEffect(() => {
if (!recordedRef.current && clerk?.telemetry) {
clerk.telemetry.record(eventMethodCalled(HOOK_NAME));
recordedRef.current = true;
}
}, [clerk]);
const [queryClient] = useClerkQueryClient();
const { queryKey, invalidationKey, stableKey, authenticated } = useMemo(() => {
const safeOrgId = params?.for === "organization" ? organization?.id : void 0;
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.CREDIT_HISTORY_KEY,
authenticated: true,
tracked: {
userId: user?.id,
orgId: safeOrgId
},
untracked: { args: { orgId: safeOrgId } }
});
}, [
user?.id,
organization?.id,
params?.for
]);
const queriesEnabled = Boolean(user?.id && billingEnabled && (params?.enabled ?? true));
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: ({ queryKey }) => {
const obj = queryKey[3];
return clerk.billing.getCreditHistory(obj.args);
},
staleTime: 1e3 * 60,
enabled: queriesEnabled
});
const revalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: invalidationKey }), [queryClient, invalidationKey]);
return {
data: query.data,
error: query.error ?? void 0,
isLoading: query.isLoading,
isFetching: query.isFetching,
revalidate
};
}
//#endregion
//#region src/react/hooks/useStatementQuery.shared.ts
function useStatementQueryCacheKeys(params) {
const { statementId, userId, orgId, for: forType } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.BILLING_STATEMENTS_KEY,
authenticated: true,
tracked: {
statementId,
forType,
userId,
orgId
},
untracked: { args: {
id: statementId ?? void 0,
orgId: orgId ?? void 0
} }
});
}, [
statementId,
forType,
userId,
orgId
]);
}
//#endregion
//#region src/react/hooks/useStatementQuery.tsx
/**
* @internal
*/
function useStatementQuery(params = {}) {
const { statementId = null, keepPreviousData = false, for: forType = "user" } = params;
const clerk = useClerkInstanceContext();
const user = useUserBase();
const organization = useOrganizationBase();
const organizationId = forType === "organization" ? organization?.id ?? null : null;
const { queryKey, stableKey, authenticated } = useStatementQueryCacheKeys({
statementId,
userId: user?.id ?? null,
orgId: organizationId,
for: forType
});
const billingEnabled = useBillingIsEnabled(params);
const queryEnabled = Boolean(statementId) && billingEnabled;
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: () => {
if (!statementId) throw new Error("statementId is required to fetch a statement");
return clerk.billing.getStatement({
id: statementId,
orgId: organizationId ?? void 0
});
},
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData),
staleTime: 1e3 * 60
});
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching
};
}
//#endregion
//#region src/react/hooks/usePlanDetailsQuery.shared.ts
function usePlanDetailsQueryCacheKeys(params) {
const { planId } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.BILLING_PLANS_KEY,
authenticated: false,
tracked: { planId: planId ?? null },
untracked: { args: { id: planId ?? void 0 } }
});
}, [planId]);
}
//#endregion
//#region src/react/hooks/usePlanDetailsQuery.tsx
/**
* @internal
*/
function __internal_usePlanDetailsQuery(params = {}) {
const { planId, initialPlan = null, keepPreviousData = true } = params;
const clerk = useClerkInstanceContext();
const targetPlanId = planId ?? initialPlan?.id ?? null;
const { queryKey } = usePlanDetailsQueryCacheKeys({ planId: targetPlanId });
const billingEnabled = useBillingIsEnabled({ authenticated: false });
const query = useClerkQuery({
queryKey,
queryFn: () => {
if (!targetPlanId) throw new Error("planId is required to fetch plan details");
return clerk.billing.getPlan({ id: targetPlanId });
},
enabled: Boolean(targetPlanId) && billingEnabled,
initialData: initialPlan ?? void 0,
placeholderData: defineKeepPreviousDataFn(keepPreviousData),
initialDataUpdatedAt: 0
});
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching
};
}
//#endregion
//#region src/react/hooks/usePaymentAttemptQuery.shared.ts
function usePaymentAttemptQueryCacheKeys(params) {
const { paymentAttemptId, userId, orgId, for: forType } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.PAYMENT_ATTEMPT_KEY,
authenticated: true,
tracked: {
paymentAttemptId,
forType,
userId,
orgId
},
untracked: { args: {
id: paymentAttemptId ?? void 0,
orgId: orgId ?? void 0
} }
});
}, [
paymentAttemptId,
forType,
userId,
orgId
]);
}
//#endregion
//#region src/react/hooks/usePaymentAttemptQuery.tsx
/**
* @internal
*/
function usePaymentAttemptQuery(params) {
const { paymentAttemptId, keepPreviousData = false, for: forType = "user" } = params;
const clerk = useClerkInstanceContext();
const user = useUserBase();
const organization = useOrganizationBase();
const organizationId = forType === "organization" ? organization?.id ?? null : null;
const { queryKey, stableKey, authenticated } = usePaymentAttemptQueryCacheKeys({
paymentAttemptId,
userId: user?.id ?? null,
orgId: organizationId,
for: forType
});
const billingEnabled = useBillingIsEnabled(params);
const queryEnabled = Boolean(paymentAttemptId) && billingEnabled;
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: ({ queryKey }) => {
const args = queryKey[3].args;
return clerk.billing.getPaymentAttempt(args);
},
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData),
staleTime: 1e3 * 60
});
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching
};
}
//#endregion
//#region src/react/hooks/useUserEnterpriseConnections.shared.ts
/**
* @internal
*/
function useUserEnterpriseConnectionsCacheKeys(params) {
const { userId, withOrganizationAccountLinking = false } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.USER_ENTERPRISE_CONNECTIONS_KEY,
authenticated: Boolean(userId),
tracked: {
userId: userId ?? null,
withOrganizationAccountLinking
},
untracked: { args: {} }
});
}, [userId, withOrganizationAccountLinking]);
}
//#endregion
//#region src/react/hooks/useUserEnterpriseConnections.tsx
/**
* Enterprise connections for the signed-in user
*
* @internal
*/
function useUserEnterpriseConnections(params = {}) {
const { keepPreviousData = true, enabled = true, withOrganizationAccountLinking = false } = params;
const clerk = useClerkInstanceContext();
const user = useUserBase();
const [queryClient] = useClerkQueryClient();
const { queryKey, stableKey, authenticated } = useUserEnterpriseConnectionsCacheKeys({
userId: user?.id ?? null,
withOrganizationAccountLinking
});
const queryEnabled = enabled && clerk.loaded && Boolean(user);
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: () => user?.getEnterpriseConnections({ withOrganizationAccountLinking }),
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData)
});
const revalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: [stableKey] }), [queryClient, stableKey]);
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
revalidate
};
}
//#endregion
//#region src/react/hooks/useOrganizationEnterpriseConnections.shared.ts
/**
* @internal
*/
function useOrganizationEnterpriseConnectionsCacheKeys(params) {
const { organizationId, withOrganizationAccountLinking = false } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY,
authenticated: Boolean(organizationId),
tracked: {
organizationId: organizationId ?? null,
withOrganizationAccountLinking
},
untracked: { args: {} }
});
}, [organizationId, withOrganizationAccountLinking]);
}
//#endregion
//#region src/react/hooks/useOrganizationEnterpriseConnections.tsx
/**
* Enterprise connections for the active organization
*
* @internal
*/
function useOrganizationEnterpriseConnections(params = {}) {
const { keepPreviousData = true, enabled = true, withOrganizationAccountLinking = false } = params;
const clerk = useClerkInstanceContext();
const organization = useOrganizationBase();
const [queryClient] = useClerkQueryClient();
const { queryKey, stableKey, authenticated } = useOrganizationEnterpriseConnectionsCacheKeys({
organizationId: organization?.id ?? null,
withOrganizationAccountLinking
});
const queryEnabled = enabled && clerk.loaded && Boolean(organization);
useClearQueriesOnSignOut({
isSignedOut: organization === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: () => organization?.getEnterpriseConnections({ withOrganizationAccountLinking }),
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData)
});
const revalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: [stableKey] }), [queryClient, stableKey]);
const createEnterpriseConnection = useCallback(async (createParams) => {
const created = await organization?.createEnterpriseConnection(createParams);
await revalidate();
return created;
}, [organization, revalidate]);
const updateEnterpriseConnection = useCallback(async (enterpriseConnectionId, updateParams) => {
const updated = await organization?.updateEnterpriseConnection(enterpriseConnectionId, updateParams);
await revalidate();
return updated;
}, [organization, revalidate]);
const deleteEnterpriseConnection = useCallback(async (enterpriseConnectionId) => {
const deleted = await organization?.deleteEnterpriseConnection(enterpriseConnectionId);
await revalidate();
return deleted;
}, [organization, revalidate]);
return {
data: query.data,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
createEnterpriseConnection,
updateEnterpriseConnection,
deleteEnterpriseConnection,
revalidate
};
}
//#endregion
//#region src/react/hooks/useOrganizationDomains.shared.ts
/**
* @internal
*/
function useOrganizationDomainsCacheKeys(params) {
const { organizationId, enrollmentMode } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.ORGANIZATION_DOMAINS_KEY,
authenticated: Boolean(organizationId),
tracked: {
organizationId: organizationId ?? null,
enrollmentMode: enrollmentMode ?? null
},
untracked: { args: {} }
});
}, [organizationId, enrollmentMode]);
}
//#endregion
//#region src/react/hooks/useOrganizationDomains.tsx
const OWNERSHIP_VERIFICATION_POLL_INTERVAL_MS = 1e4;
/**
* Domains for the active organization.
*
* @internal
*/
function useOrganizationDomains(params = {}) {
const { keepPreviousData = true, enabled = true, enrollmentMode, onOwnershipVerified } = params;
const clerk = useClerkInstanceContext();
const organization = useOrganizationBase();
const [queryClient] = useClerkQueryClient();
const onOwnershipVerifiedRef = useRef(onOwnershipVerified);
onOwnershipVerifiedRef.current = onOwnershipVerified;
const { queryKey, stableKey, authenticated } = useOrganizationDomainsCacheKeys({
organizationId: organization?.id ?? null,
enrollmentMode
});
const queryEnabled = enabled && clerk.loaded && Boolean(organization);
useClearQueriesOnSignOut({
isSignedOut: organization === null,
authenticated,
stableKeys: stableKey
});
const fetchParams = enrollmentMode ? { enrollmentMode } : void 0;
const query = useClerkQuery({
queryKey,
queryFn: () => organization?.getDomains(fetchParams),
enabled: queryEnabled,
placeholderData: defineKeepPreviousDataFn(keepPreviousData)
});
const revalidate = useCallback(() => queryClient.invalidateQueries({ queryKey: [stableKey] }), [queryClient, stableKey]);
const createDomain = useCallback(async (name) => {
let created = await organization?.createDomain(name, enrollmentMode ? { enrollmentMode } : void 0);
if (created && enrollmentMode === "enterprise_sso") created = (await organization?.prepareOwnershipVerification([created.id]))?.data[0] ?? created;
await revalidate();
return created;
}, [
organization,
revalidate,
enrollmentMode
]);
const prepareOwnershipVerification = useCallback(async (domains) => {
const prepared = await organization?.prepareOwnershipVerification(domains.map((domain) => domain.id));
await revalidate();
return prepared;
}, [organization, revalidate]);
const attemptOwnershipVerification = useCallback(async (domains) => {
const attempted = await organization?.attemptOwnershipVerification(domains.map((domain) => domain.id));
await revalidate();
return attempted;
}, [organization, revalidate]);
const response = query.data;
const unverifiedOwnershipKey = useMemo(() => (response?.data ?? []).filter((domain) => domain.ownershipVerification?.status === "unverified").map((domain) => domain.id), [response?.data]).join(",");
useEffect(() => {
if (!queryEnabled || !organization || !unverifiedOwnershipKey) return;
let cancelled = false;
let timeoutId;
const scheduleNext = () => {
timeoutId = setTimeout(() => void runAttempt(), OWNERSHIP_VERIFICATION_POLL_INTERVAL_MS);
};
const domainIds = unverifiedOwnershipKey.split(",");
const runAttempt = async () => {
const result = await organization.attemptOwnershipVerification(domainIds).catch((error) => {
logger.warnOnce(`Clerk: failed to attempt organization domain ownership verification: ${error}`);
});
if (cancelled) return;
await revalidate();
if (cancelled) return;
const verifiedDomains = result?.data.filter((domain) => domain.ownershipVerification?.status === "verified") ?? [];
if (verifiedDomains.length) await onOwnershipVerifiedRef.current?.(verifiedDomains);
if (cancelled) return;
if (!!result?.data.length && result.data.every((domain) => domain.ownershipVerification?.status === "verified")) return;
scheduleNext();
};
scheduleNext();
return () => {
cancelled = true;
clearTimeout(timeoutId);
};
}, [unverifiedOwnershipKey, queryEnabled]);
return {
data: response?.data,
totalCount: response?.total_count,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
createDomain,
prepareOwnershipVerification,
attemptOwnershipVerification,
revalidate
};
}
//#endregion
//#region src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.shared.ts
/**
* @internal
*/
function useOrganizationEnterpriseConnectionTestRunsCacheKeys(params) {
const { organizationId, enterpriseConnectionId, args } = params;
return useMemo(() => {
return createCacheKeys({
stablePrefix: INTERNAL_STABLE_KEYS.ORGANIZATION_ENTERPRISE_CONNECTION_TEST_RUNS_KEY,
authenticated: Boolean(organizationId),
tracked: {
organizationId: organizationId ?? null,
enterpriseConnectionId: enterpriseConnectionId ?? null
},
untracked: { args }
});
}, [
organizationId,
enterpriseConnectionId,
JSON.stringify(args)
]);
}
//#endregion
//#region src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
const DEFAULT_POLL_INTERVAL_MS = 2e3;
/**
* Subscribes to the list of enterprise-connection test runs for the active organization
*
* @internal
*/
function useOrganizationEnterpriseConnectionTestRuns(params) {
const { enterpriseConnectionId, params: fetchParams = {
initialPage: 1,
pageSize: 10
}, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, enabled = true, keepPreviousData = false } = params;
const clerk = useClerkInstanceContext();
const organization = useOrganizationBase();
const [queryClient] = useClerkQueryClient();
const { queryKey, invalidationKey, stableKey, authenticated } = useOrganizationEnterpriseConnectionTestRunsCacheKeys({
organizationId: organization?.id ?? null,
enterpriseConnectionId,
args: fetchParams
});
useClearQueriesOnSignOut({
isSignedOut: organization === null,
authenticated,
stableKeys: stableKey
});
const queryEnabled = enabled && clerk.loaded && Boolean(organization) && Boolean(enterpriseConnectionId);
const [shouldPoll, setShouldPoll] = useState(false);
useEffect(() => {
setShouldPoll(false);
}, [enterpriseConnectionId]);
const query = useClerkQuery({
queryKey,
queryFn: () => {
if (!enterpriseConnectionId) throw new Error("enterpriseConnectionId is required to fetch test runs");
return organization?.getEnterpriseConnectionTestRuns(enterpriseConnectionId, fetchParams);
},
refetchInterval: (q) => {
if (!shouldPoll) return false;
return (q.state.data?.data?.length ?? 0) > 0 ? false : pollIntervalMs;
},
enabled: queryEnabled,
refetchIntervalInBackground: false,
placeholderData: defineKeepPreviousDataFn(keepPreviousData)
});
const hasRows = (query.data?.data?.length ?? 0) > 0;
useEffect(() => {
if (shouldPoll && hasRows) setShouldPoll(false);
}, [shouldPoll, hasRows]);
const revalidate = useCallback(async (options) => {
if ((options?.armPolling ?? true) && !hasRows) setShouldPoll(true);
if (options?.exact) await queryClient.invalidateQueries({
queryKey,
exact: true
});
else await queryClient.invalidateQueries({ queryKey: invalidationKey });
const fresh = queryClient.getQueryData(queryKey);
return {
data: fresh?.data,
totalCount: fresh?.total_count
};
}, [
queryClient,
invalidationKey,
queryKey,
hasRows
]);
const isPolling = queryEnabled && shouldPoll && !hasRows;
return {
data: query.data?.data,
totalCount: query.data?.total_count,
error: query.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
isPolling,
revalidate
};
}
//#endregion
//#region src/react/utils.ts
function assertClerkSingletonExists(clerk) {
if (!clerk) clerkCoreErrorNoClerkSingleton();
}
//#endregion
//#region src/react/ClerkContextProvider.tsx
function ClerkContextProvider(props) {
const clerk = props.clerk;
assertClerkSingletonExists(clerk);
if (props.initialState instanceof Promise && !("use" in React && typeof React.use === "function")) throw new Error("initialState cannot be a promise if React version is less than 19");
const clerkCtx = React.useMemo(() => ({ value: clerk }), [props.clerkStatus]);
return /* @__PURE__ */ React.createElement(InitialStateProvider, { initialState: props.initialState }, /* @__PURE__ */ React.createElement(ClerkInstanceContext.Provider, { value: clerkCtx }, /* @__PURE__ */ React.createElement(__experimental_CheckoutProvider, { value: void 0 }, props.children)));
}
//#endregion
//#region src/react/stripe-react/utils.ts
const usePrevious = (value) => {
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
const useAttachEvent = (element, event, cb) => {
const cbDefined = !!cb;
const cbRef = useRef(cb);
useEffect(() => {
cbRef.current = cb;
}, [cb]);
useEffect(() => {
if (!cbDefined || !element) return () => {};
const decoratedCb = (...args) => {
if (cbRef.current) cbRef.current(...args);
};
element.on(event, decoratedCb);
return () => {
element.off(event, decoratedCb);
};
}, [
cbDefined,
event,
element,
cbRef
]);
};
//#endregion
//#region src/react/stripe-react/index.tsx
const ElementsContext = React.createContext(null);
ElementsContext.displayName = "ElementsContext";
const parseElementsContext = (ctx, useCase) => {
if (!ctx) throw new Error(`Could not find Elements context; You need to wrap the part of your app that ${useCase} in an <Elements> provider.`);
return ctx;
};
/**
* The `Elements` provider allows you to use [Element components](https://stripe.com/docs/stripe-js/react#element-components) and access the [Stripe object](https://stripe.com/docs/js/initializing) in any nested component.
* Render an `Elements` provider at the root of your React app so that it is available everywhere you need it.
*
* To use the `Elements` provider, call `loadStripe` from `@stripe/stripe-js` with your publishable key.
* The `loadStripe` function will asynchronously load the Stripe.js script and initialize a `Stripe` object.
* Pass the returned `Promise` to `Elements`.
*
* @docs https://stripe.com/docs/stripe-js/react#elements-provider
*/
const Elements = (({ stripe: rawStripeProp, options, children }) => {
const parsed = React.useMemo(() => parseStripeProp(rawStripeProp), [rawStripeProp]);
const [ctx, setContext] = React.useState(() => ({
stripe: parsed.tag === "sync" ? parsed.stripe : null,
elements: parsed.tag === "sync" ? parsed.stripe.elements(options) : null
}));
React.useEffect(() => {
let isMounted = true;
const safeSetContext = (stripe) => {
setContext((ctx) => {
if (ctx.stripe) return ctx;
return {
stripe,
elements: stripe.elements(options)
};
});
};
if (parsed.tag === "async" && !ctx.stripe) parsed.stripePromise.then((stripe) => {
if (stripe && isMounted) safeSetContext(stripe);
});
else if (parsed.tag === "sync" && !ctx.stripe) safeSetContext(parsed.stripe);
return () => {
isMounted = false;
};
}, [
parsed,
ctx,
options
]);
const prevStripe = usePrevious(rawStripeProp);
React.useEffect(() => {
if (prevStripe !== null && prevStripe !== rawStripeProp) console.warn("Unsupported prop change on Elements: You cannot change the `stripe` prop after setting it.");
}, [prevStripe, rawStripeProp]);
const prevOptions = usePrevious(options);
React.useEffect(() => {
if (!ctx.elements) return;
const updates = extractAllowedOptionsUpdates(options, prevOptions, ["clientSecret", "fonts"]);
if (updates) ctx.elements.update(updates);
}, [
options,
prevOptions,
ctx.elements
]);
return /* @__PURE__ */ React.createElement(ElementsContext.Provider, { value: ctx }, children);
});
const useElementsContextWithUseCase = (useCaseMessage) => {
return parseElementsContext(React.useContext(ElementsContext), useCaseMessage);
};
const useElements = () => {
const { elements } = useElementsContextWithUseCase("calls useElements()");
return elements;
};
const INVALID_STRIPE_ERROR = "Invalid prop `stripe` supplied to `Elements`. We recommend using the `loadStripe` utility from `@stripe/stripe-js`. See https://stripe.com/docs/stripe-js/react#elements-props-stripe for details.";
const validateStripe = (maybeStripe, errorMsg = INVALID_STRIPE_ERROR) => {
if (maybeStripe === null || isStripe(maybeStripe)) return maybeStripe;
throw new Error(errorMsg);
};
const parseStripeProp = (raw, errorMsg = INVALID_STRIPE_ERROR) => {
if (isPromise(raw)) return {
tag: "async",
stripePromise: Promise.resolve(raw).then((result) => validateStripe(result, errorMsg))
};
const stripe = validateStripe(raw, errorMsg);
if (stripe === null) return { tag: "empty" };
return {
tag: "sync",
stripe
};
};
const isUnknownObject = (raw) => {
return raw !== null && typeof raw === "object";
};
const isPromise = (raw) => {
return isUnknownObject(raw) && typeof raw.then === "function";
};
const isStripe = (raw) => {
return isUnknownObject(raw) && typeof raw.elements === "function" && typeof raw.createToken === "function" && typeof raw.createPaymentMethod === "function" && typeof raw.confirmCardPayment === "function";
};
const extractAllowedOptionsUpdates = (options, prevOptions, immutableKeys) => {
if (!isUnknownObject(options)) return null;
return Object.keys(options).reduce((newOptions, key) => {
const isUpdated = !isUnknownObject(prevOptions) || !isEqual(options[key], prevOptions[key]);
if (immutableKeys.includes(key)) {
if (isUpdated) console.warn(`Unsupported prop change: options.${key} is not a mutable property.`);
return newOptions;
}
if (!isUpdated) return newOptions;
return {
...newOptions || {},
[key]: options[key]
};
}, null);
};
const PLAIN_OBJECT_STR = "[object Object]";
const isEqual = (left, right) => {
if (!isUnknownObject(left) || !isUnknownObject(right)) return left === right;
const leftArray = Array.isArray(left);
if (leftArray !== Array.isArray(right)) return false;
const leftPlainObject = Object.prototype.toString.call(left) === PLAIN_OBJECT_STR;
if (leftPlainObject !== (Object.prototype.toString.call(right) === PLAIN_OBJECT_STR)) return false;
if (!leftPlainObject && !leftArray) return left === right;
const leftKeys = Object.keys(left);
const rightKeys = Object.keys(right);
if (leftKeys.length !== rightKeys.length) return false;
const keySet = {};
for (let i = 0; i < leftKeys.length; i += 1) keySet[leftKeys[i]] = true;
for (let i = 0; i < rightKeys.length; i += 1) keySet[rightKeys[i]] = true;
const allKeys = Object.keys(keySet);
if (allKeys.length !== leftKeys.length) return false;
const l = left;
const r = right;
const pred = (key) => {
return isEqual(l[key], r[key]);
};
return allKeys.every(pred);
};
const useStripe = () => {
const { stripe } = useElementsOrCheckoutSdkContextWithUseCase("calls useStripe()");
return stripe;
};
const useElementsOrCheckoutSdkContextWithUseCase = (useCaseString) => {
return parseElementsContext(React.useContext(ElementsContext), useCaseString);
};
const capitalized = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const createElementComponent = (type, isServer) => {
const displayName = `${capitalized(type)}Element`;
const ClientElement = ({ id, className, fallback, options = {}, onBlur, onFocus, onReady, onChange, onEscape, onClick, onLoadError, onLoaderStart, onNetworksChange, onConfirm, onCancel, onShippingAddressChange, onShippingRateChange }) => {
const ctx = useElementsOrCheckoutSdkContextWithUseCase(`mounts <${displayName}>`);
const elements = "elements" in ctx ? ctx.elements : null;
const [element, setElement] = React.useState(null);
const elementRef = React.useRef(null);
const domNode = React.useRef(null);
const [isReady, setReady] = useState(false);
useAttachEvent(element, "blur", onBlur);
useAttachEvent(element, "focus", onFocus);
useAttachEvent(element, "escape", onEscape);
useAttachEvent(element, "click", onClick);
useAttachEvent(element, "loaderror", onLoadError);
useAttachEvent(element, "loaderstart", onLoaderStart);
useAttachEvent(element, "networkschange", onNetworksChange);
useAttachEvent(element, "confirm", onConfirm);
useAttachEvent(element, "cancel", onCancel);
useAttachEvent(element, "shippingaddresschange", onShippingAddressChange);
useAttachEvent(element, "shippingratechange", onShippingRateChange);
useAttachEvent(element, "change", onChange);
let readyCallback;
if (onReady) readyCallback = () => {
setReady(true);
onReady(element);
};
useAttachEvent(element, "ready", readyCallback);
React.useLayoutEffect(() => {
if (elementRef.current === null && domNode.current !== null && elements) {
let newElement = null;
if (elements) newElement = elements.create(type, options);
elementRef.current = newElement;
setElement(newElement);
if (newElement) newElement.mount(domNode.current);
}
}, [elements, options]);
const prevOptions = usePrevious(options);
React.useEffect(() => {
if (!elementRef.current) return;
const updates = extractAllowedOptionsUpdates(options, prevOptions, ["paymentRequest"]);
if (updates && "update" in elementRef.current) elementRef.current.update(updates);
}, [options, prevOptions]);
React.useLayoutEffect(() => {
return () => {
if (elementRef.current && typeof elementRef.current.destroy === "function") try {
elementRef.current.destroy();
elementRef.current = null;
} catch {}
};
}, []);
return /* @__PURE__ */ React.createElement(React.Fragment, null, !isReady && fallback, /* @__PURE__ */ React.createElement("div", {
id,
style: {
height: isReady ? "unset" : "0px",
visibility: isReady ? "visible" : "hidden"
},
className,
ref: domNode
}));
};
const ServerElement = (props) => {
useElementsOrCheckoutSdkContextWithUseCase(`mounts <${displayName}>`);
const { id, className } = props;
return /* @__PURE__ */ React.createElement("div", {
id,
className
});
};
const Element = isServer ? ServerElement : ClientElement;
Element.displayName = displayName;
Element.__elementType = type;
return Element;
};
const PaymentElement$1 = createElementComponent("payment", typeof window === "undefined");
//#endregion
//#region src/react/billing/useInitializePaymentMethod.tsx
/**
* @internal
*/
function useInitializePaymentMethod(options) {
const { for: forType } = options ?? {};
const organization = useOrganizationBase();
const user = useUserBase();
const resource = forType === "organization" ? organization : user;
const billingEnabled = useBillingIsEnabled(options);
const stableKey = "billing-payment-method-initialize";
const authenticated = true;
const queryKey = useMemo(() => {
return [
stableKey,
authenticated,
{ resourceId: resource?.id },
{}
];
}, [resource?.id]);
const isEnabled = Boolean(resource?.id) && billingEnabled;
useClearQueriesOnSignOut({
isSignedOut: user === null,
authenticated,
stableKeys: stableKey
});
const query = useClerkQuery({
queryKey,
queryFn: async () => {
if (!resource) return;
return resource.initializePaymentMethod({ gateway: "stripe" });
},
enabled: isEnabled,
staleTime: 1e3 * 60,
refetchOnWindowFocus: false,
placeholderData: defineKeepPreviousDataFn(isEnabled)
});
const [queryClient] = useClerkQueryClient();
const initializePaymentMethod = useCallback(async () => {
if (!resource) return;
const result = await resource.initializePaymentMethod({ gateway: "stripe" });
queryClient.setQueryData(queryKey, result);
return result;
}, [
queryClient,
queryKey,
resource
]);
return {
initializedPaymentMethod: query.data ?? void 0,
initializePaymentMethod
};
}
//#endregion
//#region src/react/billing/useStripeClerkLibs.tsx
/**
* @internal
*/
function useStripeClerkLibs() {
const clerk = useClerk();
return useClerkQuery({
queryKey: ["clerk-stripe-sdk"],
queryFn: async () => {
return { loadStripe: await clerk.__internal_loadStripeJs() };
},
staleTime: Infinity,
refetchOnWindowFocus: false,
placeholderData: defineKeepPreviousDataFn(true)
}).data ?? null;
}
//#endregion
//#region src/react/billing/useStripeLoader.tsx
/**
* @internal
*/
function useStripeLoader(options) {
const { stripeClerkLibs, externalGatewayId, stripePublishableKey } = options;
const queryKey = useMemo(() => {
return ["stripe-sdk", {
externalGatewayId,
stripePublishableKey
}];
}, [externalGatewayId, stripePublishableKey]);
const billingEnabled = useBillingIsEnabled({ authenticated: true });
return useClerkQuery({
queryKey,
queryFn: () => {
if (!stripeClerkLibs || !externalGatewayId || !stripePublishableKey) return null;
return stripeClerkLibs.loadStripe(stripePublishableKey, { stripeAccount: externalGatewayId });
},
enabled: Boolean(stripeClerkLibs && externalGatewayId && stripePublishableKey) && billingEnabled,
staleTime: 1e3 * 60,
refetchOnWindowFocus: false,
placeholderData: defineKeepPreviousDataFn(true)
}).data;
}
//#endregion
//#region src/react/billing/payment-element.tsx
const useInternalEnvironment = () => {
return useClerk().__internal_environment;
};
const useLocalization = () => {
const clerk = useClerk();
let locale = "en";
try {
locale = clerk.__internal_getOption("localization")?.locale || "en";
} catch {}
return locale.split("-")[0];
};
const usePaymentSourceUtils = (forResource = "user") => {
const stripeClerkLibs = useStripeClerkLibs();
const environment = useInternalEnvironment();
const { initializedPaymentMethod, initializePaymentMethod } = useInitializePaymentMethod({ for: forResource });
const stripePublishableKey = environment?.commerceSettings.billing.stripePublishableKey ?? void 0;
return {
stripe: useStripeLoader({
stripeClerkLibs,
externalGatewayId: initializedPaymentMethod?.externalGatewayId,
stripePublishableKey
}),
initializePaymentMethod,
externalClientSecret: initializedPaymentMethod?.externalClientSecret,
paymentMethodOrder: initializedPaymentMethod?.paymentMethodOrder
};
};
const [PaymentElementContext, usePaymentElementContext] = createContextAndHook("PaymentElementContext");
const [StripeUtilsContext, useStripeUtilsContext] = createContextAndHook("StripeUtilsContext");
const ValidateStripeUtils = ({ children }) => {
const stripe = useStripe();
const elements = useElements();
return /* @__PURE__ */ React.createElement(StripeUtilsContext.Provider, { value: { value: {
stripe,
elements
} } }, children);
};
const DummyStripeUtils = ({ children }) => {
return /* @__PURE__ */ React.createElement(StripeUtilsContext.Provider, { value: { value: {} } }, children);
};
const PropsProvider = ({ children, ...props }) => {
const utils = usePaymentSourceUtils(props.for);
const [isPaymentElementReady, setIsPaymentElementReady] = useState(false);
return /* @__PURE__ */ React.createElement(PaymentElementContext.Provider, { value: { value: {
...props,
...utils,
setIsPaymentElementReady,
isPaymentElementReady
} } }, children);
};
const PaymentElementProvider = ({ children, ...props }) => {
return /* @__PURE__ */ React.createElement(PropsProvider, props, /* @__PURE__ */ React.createElement(PaymentElementInternalRoot, null, children));
};
const PaymentElementInternalRoot = (props) => {
const { stripe, externalClientSecret, stripeAppearance } = usePaymentElementContext();
const locale = useLocalization();
if (stripe && externalClientSecret) return /* @__PURE__ */ React.createElement(Elements, {
key: externalClientSecret,
stripe,
options: {
loader: "never",
clientSecret: externalClientSecret,
appearance: { variables: stripeAppearance },
locale
}
}, /* @__PURE__ */ React.createElement(ValidateStripeUtils, null, props.children));
return /* @__PURE__ */ React.createElement(DummyStripeUtils, null, props.children);
};
const PaymentElement = ({ fallback }) => {
const { setIsPaymentElementReady, paymentMethodOrder, checkout, stripe, externalClientSecret, paymentDescription, for: _for } = usePaymentElementContext();
const environment = useInternalEnvironment();
const applePay = useMemo(() => {
if (!checkout || !checkout.totals || !checkout.plan) return;
return { recurringPaymentRequest: {
paymentDescription: paymentDescription || "",
managementURL: _for === "organization" ? environment?.displayConfig.organizationProfileUrl || "" : environment?.displayConfig.userProfileUrl || "",
regularBilling: {
amount: checkout.totals.totalDueNow?.amount || checkout.totals.grandTotal.amount,
label: checkout.plan.name,
recurringPaymentIntervalUnit: checkout.planPeriod === "annual" ? "year" : "month"
}
} };
}, [
checkout,
paymentDescription,
_for,
environment
]);
const options = useMemo(() => {
return {
layout: {
type: "tabs",
defaultCollapsed: false
},
paymentMethodOrder,
applePay
};
}, [applePay, paymentMethodOrder]);
const onReady = useCallback(() => {
setIsPaymentElementReady(true);
}, [setIsPaymentElementReady]);
if (!stripe || !externalClientSecret) return /* @__PURE__ */ React.createElement(React.Fragment, null, fallback);
return /* @__PURE__ */ React.createElement(PaymentElement$1, {
fallback,
onReady,
options
});
};
const throwLibsMissingError = () => {
throw new Error("Clerk: Unable to submit, Stripe libraries are not yet loaded. Be sure to check `isFormReady` before calling `submit`.");
};
const usePaymentElement = () => {
const { isPaymentElementReady, initializePaymentMethod } = usePaymentElementContext();
const { stripe, elements } = useStripeUtilsContext();
const { externalClientSecret } = usePaymentElementContext();
const submit = useCallback(async () => {
if (!stripe || !elements) return throwLibsMissingError();
const { setupIntent, error } = await stripe.confirmSetup({
elements,
confirmParams: { return_url: window.location.href },
redirect: "if_required"
});
if (error) return {
data: null,
error: {
gateway: "stripe",
error: {
code: error.code,
message: error.message,
type: error.type
}
}
};
return {
data: {
gateway: "stripe",
paymentToken: setupIntent.payment_method
},
error: null
};
}, [stripe, elements]);
const reset = useCallback(async () => {
if (!stripe || !elements) return throwLibsMissingError();
await initializePaymentMethod();
}, [
stripe,
elements,
initializePaymentMethod
]);
const isProviderReady = Boolean(stripe && externalClientSecret);
if (!isProviderReady) return {
submit: throwLibsMissingError,
reset: throwLibsMissingError,
isFormReady: false,
provider: void 0,
isProviderReady: false
};
return {
submit,
reset,
isFormReady: isPaymentElementReady,
provider: { name: "stripe" },
isProviderReady
};
};
//#endregion
//#region src/react/PortalProvider.tsx
const [PortalContext, , usePortalContextWithoutGuarantee] = createContextAndHook("PortalProvider");
/**
* UNSAFE_PortalProvider allows you to specify a custom container for Clerk floating UI elements
* (popovers, modals, tooltips, etc.) that use portals.
*
* Only components within this provider will be affected. Components outside the provider
* will continue to use the default document.body for portals.
*
* This is particularly useful when using Clerk components inside external UI libraries
* like Radix Dialog or React Aria Components, where portaled elements need to render
* within the dialog's container to remain interactable.
*
* @example
* ```tsx
* function Example() {
* const containerRef = useRef(null);
* return (
* <RadixDialog ref={containerRef}>
* <UNSAFE_PortalProvider getContainer={() => containerRef.current}>
* <UserButton />
* </UNSAFE_PortalProvider>
* </RadixDialog>
* );
* }
* ```
*/
const UNSAFE_PortalProvider = ({ children, getContainer }) => {
const contextValue = React.useMemo(() => ({ value: { getContainer } }), [getContainer]);
return /* @__PURE__ */ React.createElement(PortalContext.Provider, { value: contextValue }, children);
};
UNSAFE_PortalProvider.displayName = "UNSAFE_PortalProvider";
/**
* Hook to get the current portal root container.
* Returns the getContainer function from context if inside a PortalProvider,
* otherwise returns a function that returns null (default behavior).
*/
const usePortalRoot = () => {
const contextValue = usePortalContextWithoutGuarantee();
if (contextValue && "getContainer" in contextValue && contextValue.getContainer) return contextValue.getContainer;
return () => null;
};
//#endregion
export { ClerkContextProvider, ClerkInstanceContext, InitialStateProvider, OptionsContext, UNSAFE_PortalProvider, __createClerkTestQueryClient, __experimental_CheckoutProvider, PaymentElement as __experimental_PaymentElement, PaymentElementProvider as __experimental_PaymentElementProvider, useCheckout as __experimental_useCheckout, usePaymentAttempts as __experimental_usePaymentAttempts, usePaymentElement as __experimental_usePaymentElement, usePaymentMethods as __experimental_usePaymentMethods, usePlans as __experimental_usePlans, useStatements as __experimental_useStatements, useSubscription as __experimental_useSubscription, useClientBase as __internal_useClientBase, __internal_useCreditBalanceQuery, __internal_useCreditHistoryQuery, useOrganizationBase as __internal_useOrganizationBase, useOrganizationDomains as __internal_useOrganizationDomains, useOrganizationEnterpriseConnectionTestRuns as __internal_useOrganizationEnterpriseConnectionTestRuns, useOrganizationEnterpriseConnections as __internal_useOrganizationEnterpriseConnections, usePaymentAttemptQuery as __internal_usePaymentAttemptQuery, __internal_usePlanDetailsQuery, useSessionBase as __internal_useSessionBase, useStatementQuery as __internal_useStatementQuery, useUserBase as __internal_useUserBase, useUserEnterpriseConnections as __internal_useUserEnterpriseConnections, __resetClerkQueryClientForTest, __setClerkQueryClientForTest, assertContextExists, createContextAndHook, getClerkQueryClient, isDeeplyEqual, useAPIKeys, useAssertWrappedByClerkProvider, useAttemptToEnableOrganizations, useClerk, useClerkInstanceContext, useDeepEqualMemo, useInitialStateContext, useOAuthConsent, useOptionsContext, useOrganization, useOrganizationCreationDefaults, useOrganizationList, usePortalRoot, useReverification, useSafeLayoutEffect, useSession, useSessionList, useUser };
//# sourceMappingURL=index.mjs.map