@clerk/shared
Version:
Internal package utils used by the Clerk SDKs
1,472 lines (1,444 loc) • 126 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const require_chunk = require('../_chunks/chunk-C_NdSu1c.js');
const require_authorization_errors = require('../authorization-errors.js');
const require_authorization = require('../authorization.js');
const require_deriveState = require('../deriveState.js');
const require_clerkRuntimeError = require('../_chunks/clerkRuntimeError-CYZdTZ_x.js');
const require_error = require('../_chunks/error-rHNfstgh.js');
const require_logger = require('../logger.js');
const require_organization = require('../organization.js');
const require_telemetry = require('../_chunks/telemetry-DZp9OI1j.js');
const require_createDeferredPromise = require('../_chunks/createDeferredPromise-BXa3LUQE.js');
const require_internal_clerk_js_errors = require('../internal/clerk-js/errors.js');
let react = require("react");
react = require_chunk.__toESM(react);
let _tanstack_query_core = require("@tanstack/query-core");
let dequal = require("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.default.createContext(void 0);
Ctx.displayName = displayName;
const useCtx = () => {
const ctx = react.default.useContext(Ctx);
assertCtxFn(ctx, `${displayName} not found`);
return ctx.value;
};
const useCtxWithoutGuarantee = () => {
const ctx = react.default.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] = (0, react.useState)(initialState);
const initialStateCtx = react.default.useMemo(() => ({ value: initialStateSnapshot }), [initialStateSnapshot]);
return /* @__PURE__ */ react.default.createElement(InitialStateContext.Provider, { value: initialStateCtx }, children);
}
function useInitialStateContext() {
const initialState = _useInitialStateContext();
if (initialState instanceof Promise) if ("use" in react.default && typeof react.default.use === "function") return react.default.use(initialState);
else throw new Error("initialState cannot be a promise if React version is less than 19");
return initialState;
}
const OptionsContext = react.default.createContext({});
const [CheckoutContext, useCheckoutContext] = createContextAndHook("CheckoutContext");
const __experimental_CheckoutProvider = ({ children, ...rest }) => {
return /* @__PURE__ */ react.default.createElement(CheckoutContext.Provider, { value: { value: rest } }, children);
};
/**
* @internal
*/
function useOptionsContext() {
const context = react.default.useContext(OptionsContext);
if (context === void 0) throw new Error("useOptions must be used within an OptionsContext");
return context;
}
/**
* @internal
*/
function useAssertWrappedByClerkProvider(displayNameOrFn) {
if (!react.default.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 _tanstack_query_core.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 _tanstack_query_core.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.useMemo(() => {
return new Observer(client, defaultedOptions);
}, [client]);
const result = observer.getOptimisticResult(defaultedOptions);
const shouldSubscribe = options.subscribed !== false;
react.useSyncExternalStore(react.useCallback((onStoreChange) => {
const unsubscribe = shouldSubscribe ? observer.subscribe(_tanstack_query_core.notifyManager.batchCalls(onStoreChange)) : _tanstack_query_core.noop;
observer.updateResult();
return unsubscribe;
}, [observer, shouldSubscribe]), () => observer.getCurrentResult(), () => observer.getCurrentResult());
react.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, _tanstack_query_core.InfiniteQueryObserver);
}
//#endregion
//#region src/react/query/useQuery.ts
/**
*
*/
function useClerkQuery(options) {
return useBaseQuery(options, _tanstack_query_core.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 = (0, react.useRef)(value);
const previousRef = (0, react.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 = (0, react.useRef)(stableKeys);
const [queryClient] = useClerkQueryClient();
const previousIsSignedIn = usePreviousValue(!isSignedOut);
(0, react.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 = (0, react.useRef)(shouldUseDefaults ? defaultValues.initialPage : params?.initialPage ?? defaultValues.initialPage);
const pageSizeRef = (0, react.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] = (0, react.useState)(config.initialPage ?? 1);
const initialPageRef = (0, react.useRef)(config.initialPage ?? 1);
const pageSizeRef = (0, react.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] = (0, react.useState)(0);
const forceUpdate = (0, react.useCallback)((updater) => {
setForceUpdateCounter(updater);
}, []);
const pagesQueryKey = (0, react.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 = (0, react.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 } = (0, react.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 = (0, react.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 = (0, react.useCallback)(() => {
if (triggerInfinite) {
infiniteQuery.fetchNextPage({ cancelRefetch: false });
return;
}
setPaginatedPage((n) => Math.max(0, n + 1));
}, [infiniteQuery, triggerInfinite]);
const fetchPrevious = (0, react.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(require_telemetry.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 = (0, react.useCallback)(() => initialState?.user, [initialState?.user]);
return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => {
return clerk.addListener(callback, { skipInitialEmit: true });
}, [clerk]), (0, react.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 (0, react.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(require_telemetry.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 = (0, react.useCallback)(() => initialState?.organization, [initialState?.organization]);
return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), (0, react.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 = (0, react.useCallback)(() => {
return initialState ? require_deriveState.deriveFromSsrInitialState(initialState)?.session : void 0;
}, [initialState]);
return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), (0, react.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 = (0, react.useRef)(false);
(0, react.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(require_telemetry.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: require_organization.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 (0, react.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(require_telemetry.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 fetc