diff --git a/docs/_partials/billing/add-new-payment-method.mdx b/docs/_partials/billing/add-new-payment-method.mdx index 80e5bde2f6..7a310509e0 100644 --- a/docs/_partials/billing/add-new-payment-method.mdx +++ b/docs/_partials/billing/add-new-payment-method.mdx @@ -3,80 +3,162 @@ The following example demonstrates how to create a billing page where a user can - **``**: Sets up the ``, which specifies that the payment actions within its children are `for` the `user`. - **``**: Renders the payment form and handles the submission logic. It uses `usePaymentElement()` to get the `submit` function and `useUser()` to get the `user` object. When the form is submitted, it first creates a payment token and then attaches it to the user. -", ""]}> - ```tsx {{ filename: 'app/user/billing/page.tsx' }} - import { ClerkLoaded } from '@clerk/nextjs' - import { PaymentElementProvider } from '@clerk/nextjs/experimental' - import { AddPaymentMethodForm } from './AddPaymentMethodForm' - - export default function Page() { - return ( -
-

Billing Settings

- - - - - - -
- ) - } - ``` - - ```tsx {{ filename: 'app/user/billing/AddPaymentMethodForm.tsx' }} - 'use client' - import { useUser } from '@clerk/nextjs' - import { usePaymentElement, PaymentElement } from '@clerk/nextjs/experimental' - import { useState } from 'react' - - export function AddPaymentMethodForm() { - const { user } = useUser() - const { submit, isFormReady } = usePaymentElement() - const [isSubmitting, setIsSubmitting] = useState(false) - const [error, setError] = useState(null) - - const handleAddPaymentMethod = async (e: React.FormEvent) => { - e.preventDefault() - if (!isFormReady || !user) { - return - } + + ", ""]}> + ```tsx {{ filename: 'app/user/billing/page.tsx' }} + import { ClerkLoaded } from '@clerk/nextjs' + import { PaymentElementProvider } from '@clerk/nextjs/experimental' + import { AddPaymentMethodForm } from './AddPaymentMethodForm' + + export default function Page() { + return ( +
+

Billing Settings

- setError(null) - setIsSubmitting(true) + + + + + +
+ ) + } + ``` - try { - // 1. Submit the form to the payment provider to get a payment token - const { data, error } = await submit() + ```tsx {{ filename: 'app/user/billing/AddPaymentMethodForm.tsx', collapsible: true }} + 'use client' - // Usually a validation error from stripe that you can ignore. - if (error) { - setIsSubmitting(false) + import { useUser } from '@clerk/nextjs' + import { usePaymentElement, PaymentElement } from '@clerk/nextjs/experimental' + import { useState } from 'react' + + export function AddPaymentMethodForm() { + const { user } = useUser() + const { submit, isFormReady } = usePaymentElement() + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState(null) + + const handleAddPaymentMethod = async (e: React.FormEvent) => { + e.preventDefault() + if (!isFormReady || !user) { return } - // 2. Use the token to add the payment source to the user - await user.addPaymentSource(data) + setError(null) + setIsSubmitting(true) + + try { + // 1. Submit the form to the payment provider to get a payment token + const { data, error } = await submit() + + // Usually a validation error from stripe that you can ignore. + if (error) { + setIsSubmitting(false) + return + } - // 3. Handle success (e.g., show a confirmation, clear the form) - alert('Payment method added successfully!') - } catch (err: any) { - setError(err.message || 'An unexpected error occurred.') - } finally { - setIsSubmitting(false) + // 2. Use the token to add the payment source to the user + await user.addPaymentSource(data) + + // 3. Handle success (e.g., show a confirmation, clear the form) + alert('Payment method added successfully!') + } catch (err: any) { + setError(err.message || 'An unexpected error occurred.') + } finally { + setIsSubmitting(false) + } } + + return ( +
+

Add a new payment method

+ + + {error &&

{error}

} + + ) + } + ``` +
+
+ + + ", ""]}> + ```tsx {{ filename: 'src/pages/user/billing/page.tsx' }} + import { ClerkLoaded } from '@clerk/clerk-react' + import { PaymentElementProvider } from '@clerk/clerk-react/experimental' + import { AddPaymentMethodForm } from './AddPaymentMethodForm' + + export default function Page() { + return ( +
+

Billing Settings

+ + + + + + +
+ ) } + ``` + + ```tsx {{ filename: 'src/pages/user/billing/AddPaymentMethodForm.tsx', collapsible: true }} + import { useUser } from '@clerk/clerk-react' + import { usePaymentElement, PaymentElement } from '@clerk/clerk-react/experimental' + import { useState } from 'react' + + export function AddPaymentMethodForm() { + const { user } = useUser() + const { submit, isFormReady } = usePaymentElement() + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState(null) + + const handleAddPaymentMethod = async (e: React.FormEvent) => { + e.preventDefault() + if (!isFormReady || !user) { + return + } + + setError(null) + setIsSubmitting(true) + + try { + // 1. Submit the form to the payment provider to get a payment token + const { data, error } = await submit() - return ( -
-

Add a new payment method

- - - {error &&

{error}

} - - ) - } - ``` -
+ // Usually a validation error from stripe that you can ignore. + if (error) { + setIsSubmitting(false) + return + } + + // 2. Use the token to add the payment source to the user + await user.addPaymentSource(data) + + // 3. Handle success (e.g., show a confirmation, clear the form) + alert('Payment method added successfully!') + } catch (err: any) { + setError(err.message || 'An unexpected error occurred.') + } finally { + setIsSubmitting(false) + } + } + + return ( +
+

Add a new payment method

+ + + {error &&

{error}

} + + ) + } + ``` +
+ diff --git a/docs/_partials/billing/use-checkout-options.mdx b/docs/_partials/billing/use-checkout-options.mdx deleted file mode 100644 index 3c1075f36b..0000000000 --- a/docs/_partials/billing/use-checkout-options.mdx +++ /dev/null @@ -1,20 +0,0 @@ - - - `for?` - - `'user' | 'organization'` - - Specifies if the checkout is for an organization. If omitted, the checkout defaults to the current user. - - --- - - - `planId` - - `string` - - The ID of the subscription plan to check out (e.g. `cplan_xxx`). - - --- - - - `planPeriod` - - `'month' | 'annual'` - - The billing period for the plan. - diff --git a/docs/guides/development/custom-flows/billing/add-new-payment-method.mdx b/docs/guides/development/custom-flows/billing/add-new-payment-method.mdx index a6d4a32e25..e1a105cca4 100644 --- a/docs/guides/development/custom-flows/billing/add-new-payment-method.mdx +++ b/docs/guides/development/custom-flows/billing/add-new-payment-method.mdx @@ -25,9 +25,5 @@ For the custom flow that allows users to add a new payment method **during check 1. Use the [`usePaymentElement()`](/docs/reference/hooks/use-payment-element) hook to submit the form and create a payment token. 1. Use the [`useUser()`](/docs/reference/hooks/use-user) hook to attach the newly created payment method to the user. - - - - - + diff --git a/docs/guides/development/upgrading/upgrade-guides/core-2/react.mdx b/docs/guides/development/upgrading/upgrade-guides/core-2/react.mdx index 82505d23ea..faded75d62 100644 --- a/docs/guides/development/upgrading/upgrade-guides/core-2/react.mdx +++ b/docs/guides/development/upgrading/upgrade-guides/core-2/react.mdx @@ -554,7 +554,7 @@ As part of this major version, a number of previously deprecated props, argument - The `membershipList` param from the `useOrganization` hook has been removed. Instead, [use the `memberships` param](/docs/reference/hooks/use-organization#properties). Examples of each can be seen here: + The `membershipList` param from the `useOrganization` hook has been removed. Instead, [use the `memberships` param](/docs/reference/hooks/use-organization). Examples of each can be seen here: ```js // before diff --git a/docs/reference/hooks/use-auth.mdx b/docs/reference/hooks/use-auth.mdx index c3a88e0313..78b92253ed 100644 --- a/docs/reference/hooks/use-auth.mdx +++ b/docs/reference/hooks/use-auth.mdx @@ -1,7 +1,308 @@ --- title: useAuth() description: Access and manage authentication state in your application with Clerk's useAuth() hook. -sdk: astro, chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: astro, chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +The `useAuth()` hook provides access to the current user's authentication state and methods to manage the active session. + +> [!NOTE] +> To access auth data server-side, see the [`Auth` object reference doc](/docs/reference/backend/types/auth-object). + + + By default, Next.js opts all routes into static rendering. If you need to opt a route or routes into dynamic rendering because you need to access the authentication data at request time, you can create a boundary by passing the `dynamic` prop to ``. See the [guide on rendering modes](/docs/guides/development/rendering-modes) for more information, including code examples. + + + + + + +## Example + +The following example demonstrates how to use the `useAuth()` hook to access the current auth state, like whether the user is signed in or not. It also includes a basic example for using the `getToken()` method to retrieve a session token for fetching data from an external resource. + + + ```tsx {{ filename: 'src/pages/ExternalDataPage.tsx' }} + import { useAuth } from '@clerk/clerk-react' + + export default function ExternalDataPage() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + + const fetchExternalData = async () => { + const token = await getToken() + + // Fetch data from an external API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return response.json() + } + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

+ Hello, {userId}! Your current active session is {sessionId}. +

+ +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/external-data/page.tsx' }} + 'use client' + + import { useAuth } from '@clerk/nextjs' + + export default function Page() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + + const fetchExternalData = async () => { + const token = await getToken() + + // Fetch data from an external API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return response.json() + } + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

+ Hello, {userId}! Your current active session is {sessionId}. +

+ +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useAuth } from '@clerk/react-router' + + export default function Home() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + + const fetchExternalData = async () => { + const token = await getToken() + + // Fetch data from an external API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return response.json() + } + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

+ Hello, {userId}! Your current active session is {sessionId}. +

+ +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/components/external-data.jsx' }} + import { useAuth } from '@clerk/astro/react' + import { useState } from 'react' + + export default function ExternalData() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + const [data, setData] = useState(null) + + const fetchExternalData = async () => { + const token = await getToken() + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + const json = await response.json() + setData(json) + } + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

+ Hello, {userId}! Your current active session is {sessionId}. +

+ + + {data &&
{JSON.stringify(data, null, 2)}
} +
+ ) + } + ``` + + ```astro {{ filename: 'src/pages/auth.astro' }} + --- + import ExternalData from '../components/external-data' + --- + + + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useAuth } from '@clerk/chrome-extension' + + export default function ExternalDataPage() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + + const fetchExternalData = async () => { + const token = await getToken() + + // Fetch data from an external API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return response.json() + } + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

+ Hello, {userId}! Your current active session is {sessionId}. +

+ +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useAuth } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: ExternalDataPage, + }) + + function ExternalDataPage() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + + const fetchExternalData = async () => { + const token = await getToken() + + // Fetch data from an external API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return response.json() + } + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

+ Hello, {userId}! Your current active session is {sessionId}. +

+ +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/external-data.tsx' }} + import { useAuth } from '@clerk/clerk-expo' + import { Text, View, TouchableOpacity } from 'react-native' + + export default function ExternalData() { + const { userId, sessionId, getToken, isLoaded, isSignedIn } = useAuth() + + const fetchExternalData = async () => { + const token = await getToken() + + // Fetch data from an external API + const response = await fetch('https://api.example.com/data', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + return response.json() + } + + // Handle loading state + if (!isLoaded) return Loading... + + // Protect the page from unauthenticated users + if (!isSignedIn) return Sign in to view this page + + return ( + + + Hello, {userId}! Your current active session is {sessionId}. + + + Fetch Data + + + ) + } + ``` + diff --git a/docs/reference/hooks/use-checkout.mdx b/docs/reference/hooks/use-checkout.mdx index 1670a3d375..67a8d145f6 100644 --- a/docs/reference/hooks/use-checkout.mdx +++ b/docs/reference/hooks/use-checkout.mdx @@ -13,156 +13,17 @@ There are two ways to use `useCheckout()`: 1. In conjunction with [``](#checkout-provider) to create a shared checkout context. All child components inside the provider can then use `useCheckout()` to access or update the same checkout state. 1. On its own by passing configuration options directly to it. This is ideal for self-contained components that handle their own checkout flow without needing a shared context. -## Parameters - -`useCheckout()` accepts a single object with the following properties: - - - - `options?` - - [`UseCheckoutOptions`](#use-checkout-options) - - An object containing the configuration for the checkout flow. - - **Required** if the hook is used without a `` wrapping the component tree. - + ### `UseCheckoutOptions` - + ## Returns `useCheckout()` returns a `{ checkout }` object. The `checkout` object contains the following properties. They are `null` until the checkout process is started by calling the `start()` method. - - - `id` - - `string | null` - - The unique identifier for the checkout session. - - --- - - - `externalClientSecret` - - `string | null` - - The client secret from an external payment provider (such as Stripe) used to complete the payment on the client-side. - - --- - - - `externalGatewayId` - - `string | null` - - The identifier for the external payment gateway used for the checkout session. - - --- - - - `paymentSource` - - [BillingPaymentMethodResource](/docs/reference/javascript/types/billing-payment-method-resource) | null - - The payment source being used for the checkout. - - --- - - - `plan` - - [BillingPlanResource](/docs/reference/javascript/types/billing-plan-resource) | null - - The subscription plan details for the checkout. - - --- - - - `planPeriod` - - `'month' | 'annual' | null` - - The billing period for the plan. - - --- - - - `planPeriodStart` - - `number | undefined` - - The start date of the plan period, represented as a Unix timestamp. - - --- - - - `status` - - `'needs_initialization' | 'needs_confirmation' | 'completed'` - - The current status of the checkout session. The following statuses are possible: - - - `needs_initialization`: The checkout hasn't started but the hook is mounted. Call `start()` to continue. - - `needs_confirmation`: The checkout has been initialized and is awaiting confirmation. Call `confirm()` to continue. - - `completed`: The checkout has been successfully confirmed. Call `finalize()` to complete the checkout. - - --- - - - `totals` - - [`BillingCheckoutTotals`](/docs/reference/javascript/types/billing-checkout-totals) - - The total costs, taxes, and other pricing details. - - --- - - - `isImmediatePlanChange` - - `boolean` - - A boolean that indicates if the plan change will take effect immediately. - - --- - - - `isStarting` - - `boolean` - - A boolean that indicates if the `start()` method is in progress. - - --- - - - `isConfirming` - - `boolean` - - A boolean that indicates if the `confirm()` method is in progress. - - --- - - - `error` - - [ClerkAPIResponseError](/docs/reference/javascript/types/clerk-api-response-error) | null - - Returns an error object if any part of the checkout process fails. - - --- - - - `fetchStatus` - - `'idle' | 'fetching' | 'error'` - - The data fetching status. - - --- - - - `start()` - - `() => Promise<{ data: BillingCheckoutResource; error: null; } | { data: null; error: ClerkAPIResponseError; }>` - - A function that initializes the checkout process by creating a checkout resource on the server. - - --- - - - `confirm()` - - `(params: ConfirmCheckoutParams) => Promise<{ data: BillingCheckoutResource; error: null; } | { data: null; error: ClerkAPIResponseError; }>` - - A function that confirms and finalizes the checkout process, usually after the user has provided and validated payment information. - - --- - - - `finalize()` - - `(params?: { navigate?: SetActiveNavigate }) => void` - - A function that finalizes the checkout process. Can optionally accept a `navigate()` function to redirect the user after completion. - - --- - - - `clear()` - - `() => void` - - A function that clears the current checkout state from the cache. - + ## `` @@ -170,7 +31,9 @@ The `` component is a wrapper that provides a checkout conte ### Properties - +The `` component accepts the following props: + + ## Usage @@ -198,124 +61,247 @@ The `useCheckout()` hook can be used with a context provider for managing state ", ""]}> - ```tsx {{ filename: 'src/components/SubscriptionPage.tsx', collapsible: true }} - import { CheckoutProvider } from '@clerk/nextjs/experimental' - import { ClerkLoaded } from '@clerk/nextjs' - import { CheckoutFlow } from './CheckoutFlow' - - export function SubscriptionPage() { - // `` sets the context for the checkout flow. - // Any child component can now call `useCheckout()` to access this context. - return ( - -
-

Upgrade Your Plan

-

You are about to subscribe to our monthly plan

- - - -
-
- ) - } - ``` + + ```tsx {{ filename: 'src/components/SubscriptionPage.tsx', collapsible: true }} + import { CheckoutProvider } from '@clerk/nextjs/experimental' + import { ClerkLoaded } from '@clerk/nextjs' + import { CheckoutFlow } from './CheckoutFlow' + + export default function SubscriptionPage() { + // `` sets the context for the checkout flow. + // Any child component can now call `useCheckout()` to access this context. + return ( + +
+

Upgrade Your Plan

+

You are about to subscribe to our monthly plan

+ + + +
+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/components/SubscriptionPage.tsx', collapsible: true }} + import { CheckoutProvider } from '@clerk/clerk-react/experimental' + import { ClerkLoaded } from '@clerk/clerk-react' + import { CheckoutFlow } from './CheckoutFlow' + + export default function SubscriptionPage() { + // `` sets the context for the checkout flow. + // Any child component can now call `useCheckout()` to access this context. + return ( + +
+

Upgrade Your Plan

+

You are about to subscribe to our monthly plan

+ + + +
+
+ ) + } + ``` +
- ```tsx {{ filename: 'src/components/CheckoutFlow.tsx', collapsible: true }} - 'use client' + + ```tsx {{ filename: 'src/components/CheckoutFlow.tsx', collapsible: true }} + 'use client' - import { useCheckout } from '@clerk/nextjs/experimental' - import { useRouter } from 'next/navigation' + import { useCheckout } from '@clerk/nextjs/experimental' + import { useState } from 'react' + import { useRouter } from 'next/navigation' - export function CheckoutFlow() { - const { checkout } = useCheckout() - const { status } = checkout + export function CheckoutFlow() { + const { checkout } = useCheckout() + const { status } = checkout - if (status === 'needs_initialization') { - return - } + if (status === 'needs_initialization') { + return + } - return ( -
- - -
- ) - } + return ( +
+ + +
+ ) + } - function CheckoutInitialization() { - const { checkout } = useCheckout() - const { start, fetchStatus } = checkout + function CheckoutInitialization() { + const { checkout } = useCheckout() + const { start, fetchStatus } = checkout - return ( - - ) - } + return ( + + ) + } - function PaymentSection() { - const { checkout } = useCheckout() - - const { isConfirming, confirm, finalize, error } = checkout - - const [isProcessing, setIsProcessing] = useState(false) - const [paymentMethodId, setPaymentMethodId] = useState(null) - - const router = useRouter() - - const submitSelectedMethod = async () => { - if (isProcessing || !paymentMethodId) return - setIsProcessing(true) - - try { - // Confirm checkout with payment method - await confirm({ - paymentSourceId: paymentMethodId, - }) - // Calling `.finalize` enables you to sync the client-side state with the server-side state of your users. - // It revalidates all authorization checks computed within server components. - await finalize({ - navigate: () => router.push('/dashboard'), - }) - } catch (error) { - console.error('Payment failed:', error) - } finally { - setIsProcessing(false) + function PaymentSection() { + const { checkout } = useCheckout() + + const { isConfirming, confirm, finalize, error } = checkout + + const [isProcessing, setIsProcessing] = useState(false) + const [paymentMethodId, setPaymentMethodId] = useState(null) + const router = useRouter() + + const submitSelectedMethod = async () => { + if (isProcessing || !paymentMethodId) return + setIsProcessing(true) + + try { + // Confirm checkout with payment method + await confirm({ + paymentSourceId: paymentMethodId, + }) + // Calling `.finalize` enables you to sync the client-side state with the server-side state of your users. + // It revalidates all authorization checks computed within server components. + await finalize({ + navigate: () => router.push('/dashboard'), + }) + } catch (error) { + console.error('Payment failed:', error) + } finally { + setIsProcessing(false) + } } + + return ( + <> + {/* A component that lists a user's payment methods and allows them to select one. See an example: https://clerk.com/docs/reference/hooks/use-payment-methods#examples */} + + + {error &&
{error.message}
} + + + + ) } - return ( - <> - {/* A component that lists a user's payment methods and allows them to select one. See an example: https://clerk.com/docs/reference/hooks/use-payment-methods#examples */} - + function CheckoutSummary() { + const { checkout } = useCheckout() + const { plan, totals } = checkout + + return ( +
+

Order Summary

+ {plan?.name} + + {totals?.totalDueNow.currencySymbol} + {totals?.totalDueNow.amountFormatted} + +
+ ) + } + ``` +
- {error &&
{error.message}
} + + ```tsx {{ filename: 'src/components/CheckoutFlow.tsx', collapsible: true }} + import { useCheckout } from '@clerk/clerk-react/experimental' + import { useState } from 'react' + import { useNavigate } from 'react-router-dom' - - - ) - } + ) + } - function CheckoutSummary() { - const { checkout } = useCheckout() - const { plan, totals } = checkout - - return ( -
-

Order Summary

- {plan?.name} - - {totals?.totalDueNow.currencySymbol} - {totals?.totalDueNow.amountFormatted} - -
- ) - } - ``` + function PaymentSection() { + const { checkout } = useCheckout() + + const { isConfirming, confirm, finalize, error } = checkout + + const [isProcessing, setIsProcessing] = useState(false) + const [paymentMethodId, setPaymentMethodId] = useState(null) + const navigate = useNavigate() + + const submitSelectedMethod = async () => { + if (isProcessing || !paymentMethodId) return + setIsProcessing(true) + + try { + // Confirm checkout with payment method + await confirm({ + paymentSourceId: paymentMethodId, + }) + // Calling `.finalize` enables you to sync the client-side state with the server-side state of your users. + // It revalidates all authorization checks computed within server components. + await finalize({ + navigate: () => navigate('/dashboard'), + }) + } catch (error) { + console.error('Payment failed:', error) + } finally { + setIsProcessing(false) + } + } + + return ( + <> + {/* A component that lists a user's payment methods and allows them to select one. See an example: https://clerk.com/docs/reference/hooks/use-payment-methods#examples */} + + + {error &&
{error.message}
} + + + + ) + } + + function CheckoutSummary() { + const { checkout } = useCheckout() + const { plan, totals } = checkout + + return ( +
+

Order Summary

+ {plan?.name} + + {totals?.totalDueNow.currencySymbol} + {totals?.totalDueNow.amountFormatted} + +
+ ) + } + ``` +
@@ -325,48 +311,93 @@ The `useCheckout()` hook can be used with a context provider for managing state The following example shows an `` component that manages its own checkout flow. - ```tsx {{ filename: 'src/components/UpgradeButton.tsx' }} - 'use client' + + ```tsx {{ filename: 'src/components/UpgradeButton.tsx' }} + 'use client' - import { useCheckout } from '@clerk/nextjs/experimental' + import { useCheckout } from '@clerk/nextjs/experimental' - export function UpgradeButton({ - planId, - planPeriod, - }: { - planId: string - planPeriod: 'month' | 'annual' - }) { - // Pass options directly to the hook when not using a provider. - const { checkout } = useCheckout({ + export function UpgradeButton({ planId, planPeriod, - for: 'user', - }) - - const { start, status, isStarting, error } = checkout - - const handleStartCheckout = async () => { - try { - await start() - // In a real app, you would now use the `externalClientSecret` - // from the checkout object to render a payment form. - console.log('Checkout started! Status:', checkout.status) - } catch (e) { - console.error('Error starting checkout:', e) + }: { + planId: string + planPeriod: 'month' | 'annual' + }) { + // Pass options directly to the hook when not using a provider. + const { checkout } = useCheckout({ + planId, + planPeriod, + for: 'user', + }) + + const { start, status, isStarting, error } = checkout + + const handleStartCheckout = async () => { + try { + await start() + // In a real app, you would now use the `externalClientSecret` + // from the checkout object to render a payment form. + console.log('Checkout started! Status:', checkout.status) + } catch (e) { + console.error('Error starting checkout:', e) + } } + + return ( +
+ + {error &&

Error: {error.errors[0].message}

} +
+ ) } + ``` +
+ + + ```tsx {{ filename: 'src/components/UpgradeButton.tsx' }} + import { useCheckout } from '@clerk/clerk-react/experimental' - return ( -
- - {error &&

Error: {error.errors[0].message}

} -
- ) - } - ``` + export function UpgradeButton({ + planId, + planPeriod, + }: { + planId: string + planPeriod: 'month' | 'annual' + }) { + // Pass options directly to the hook when not using a provider. + const { checkout } = useCheckout({ + planId, + planPeriod, + for: 'user', + }) + + const { start, status, isStarting, error } = checkout + + const handleStartCheckout = async () => { + try { + await start() + // In a real app, you would now use the `externalClientSecret` + // from the checkout object to render a payment form. + console.log('Checkout started! Status:', checkout.status) + } catch (e) { + console.error('Error starting checkout:', e) + } + } + + return ( +
+ + {error &&

Error: {error.errors[0].message}

} +
+ ) + } + ``` +
diff --git a/docs/reference/hooks/use-clerk.mdx b/docs/reference/hooks/use-clerk.mdx index 26e922f2d2..6217300d2f 100644 --- a/docs/reference/hooks/use-clerk.mdx +++ b/docs/reference/hooks/use-clerk.mdx @@ -1,7 +1,102 @@ --- title: useClerk() description: Access and manage the Clerk object in your React application with Clerk's useClerk() hook. -sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +> [!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`](/docs/reference/javascript/clerk) object, allowing you to build alternatives to any Clerk Component. + + + +## Example + +The following example uses the `useClerk()` hook to access the `clerk` object. The `clerk` object is used to call the [`openSignIn()`](/docs/reference/javascript/clerk#sign-in) method to open the sign-in modal. + + + ```tsx {{ filename: 'src/pages/Example.tsx' }} + import { useClerk } from '@clerk/clerk-react' + + export default function Example() { + const clerk = useClerk() + + return + } + ``` + + + + ```tsx {{ filename: 'app/page.tsx' }} + 'use client' + + import { useClerk } from '@clerk/nextjs' + + export default function Page() { + const clerk = useClerk() + + return + } + ``` + + + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useClerk } from '@clerk/react-router' + + export default function Home() { + const clerk = useClerk() + + return + } + ``` + + + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useClerk } from '@clerk/chrome-extension' + + export default function Home() { + const clerk = useClerk() + + return + } + ``` + + + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useClerk } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: Home, + }) + + export default function Home() { + const clerk = useClerk() + + return + } + ``` + + + + ```tsx {{ filename: 'app/(clerk)/index.tsx' }} + import { useClerk } from '@clerk/clerk-expo' + import { Text, View, TouchableOpacity } from 'react-native' + + export default function Page() { + const clerk = useClerk() + + return ( + + clerk.openSignIn({})}> + Sign in + + + ) + } + ``` + diff --git a/docs/reference/hooks/use-organization-list.mdx b/docs/reference/hooks/use-organization-list.mdx index 81441df8fc..45f0da002a 100644 --- a/docs/reference/hooks/use-organization-list.mdx +++ b/docs/reference/hooks/use-organization-list.mdx @@ -6,6 +6,37 @@ sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react- 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](!active-organization). +## Parameters + +`useOrganizationList()` accepts a single object with the following properties: + + + +> [!WARNING] +> By default, the `userMemberships`, `userInvitations`, and `userSuggestions` attributes are not populated. To fetch and paginate the data, you must pass `true` or an object with the desired properties. + +### Shared properties + +Optional properties that are shared across the `userMemberships`, `userInvitations`, and `userSuggestions` properties. + + + + + +## Returns + + + +### `CreateOrganizationParams` + + + +### `PaginatedResources` + + + +To see the different organization features integrated into one application, take a look at our [organizations demo repository](https://github.com/clerk/organizations-demo). + ## Examples ### Expanding and paginating attributes @@ -53,9 +84,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!isLoaded) { - return <>Loading - } + // Handle loading state + if (!isLoaded) return
Loading...
return ( <> @@ -92,9 +122,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!isLoaded) { - return <>Loading - } + // Handle loading state + if (!isLoaded) return
Loading...
return ( <> @@ -127,9 +156,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!isLoaded) { - return <>Loading - } + // Handle loading state + if (!isLoaded) return
Loading...
return ( <> @@ -162,44 +190,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!isLoaded) { - return <>Loading - } - - return ( - <> -
    - {userMemberships.data?.map((mem) => ( -
  • - {mem.organization.name} - -
  • - ))} -
- - - - ) - } - ``` - - - - ```tsx {{ filename: 'components/JoinedOrganizations.tsx' }} - import { useOrganizationList } from '@clerk/remix' - - export function JoinedOrganizations() { - const { isLoaded, setActive, userMemberships } = useOrganizationList({ - userMemberships: { - infinite: true, - }, - }) - - if (!isLoaded) { - return <>Loading - } + // Handle loading state + if (!isLoaded) return
Loading...
return ( <> @@ -232,9 +224,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!isLoaded) { - return <>Loading - } + // Handle loading state + if (!isLoaded) return
Loading...
return ( <> @@ -268,9 +259,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!isLoaded) { - return Loading - } + // Handle loading state + if (!isLoaded) return Loading... return ( @@ -316,9 +306,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!isLoaded || userInvitations.isLoading) { - return <>Loading - } + // Handle loading state + if (!isLoaded || userInvitations.isLoading) return
Loading...
return ( <> @@ -368,9 +357,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!isLoaded || userInvitations.isLoading) { - return <>Loading - } + // Handle loading state + if (!isLoaded || userInvitations.isLoading) return
Loading...
return ( <> @@ -416,9 +404,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!isLoaded || userInvitations.isLoading) { - return <>Loading - } + // Handle loading state + if (!isLoaded || userInvitations.isLoading) return
Loading...
return ( <> @@ -464,57 +451,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!isLoaded || userInvitations.isLoading) { - return <>Loading - } - - return ( - <> - - - - - - - - - - {userInvitations.data?.map((inv) => ( - - - - - ))} - -
EmailOrg name
{inv.emailAddress}{inv.publicOrganizationData.name}
- - - - - ) - } - ``` -
- - - ```tsx {{ filename: 'components/UserInvitationsTable.tsx' }} - import { useOrganizationList } from '@clerk/remix' - - export function UserInvitationsTable() { - const { isLoaded, userInvitations } = useOrganizationList({ - userInvitations: { - infinite: true, - keepPreviousData: true, - }, - }) - - if (!isLoaded || userInvitations.isLoading) { - return <>Loading - } + // Handle loading state + if (!isLoaded || userInvitations.isLoading) return
Loading...
return ( <> @@ -560,9 +498,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!isLoaded || userInvitations.isLoading) { - return <>Loading - } + // Handle loading state + if (!isLoaded || userInvitations.isLoading) return
Loading...
return ( <> @@ -608,9 +545,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!isLoaded || userInvitations.isLoading) { - return Loading - } + // Handle loading state + if (!isLoaded || userInvitations.isLoading) return Loading... return ( @@ -643,33 +579,19 @@ Notice the difference between this example's pagination and the infinite paginat ```
-## Properties - -`useOrganizationList()` accepts a single object with the following properties: - - +## Related guides -> [!WARNING] -> By default, the `userMemberships`, `userInvitations`, and `userSuggestions` attributes are not populated. To fetch and paginate the data, you must pass `true` or an object with the desired properties. + + - [Build a custom organization switcher](/docs/guides/development/custom-flows/organizations/organization-switcher) + - Use Clerk's API to build a custom flow for switching between organizations -### Shared properties + --- -Optional properties that are shared across the `userMemberships`, `userInvitations`, and `userSuggestions` properties. + - [Create organizations](/docs/guides/development/custom-flows/organizations/create-organizations) + - Use Clerk's API to build a custom flow for creating organizations - + --- - - -## Returns - - - -### `CreateOrganizationParams` - - - -### `PaginatedResources` - - - -To see the different organization features integrated into one application, take a look at our [organizations demo repository](https://github.com/clerk/organizations-demo). + - [Manage a user's organization invitations](/docs/guides/development/custom-flows/organizations/manage-user-org-invitations) + - Use Clerk's API to build a custom flow for managing user's organization invitations + diff --git a/docs/reference/hooks/use-organization.mdx b/docs/reference/hooks/use-organization.mdx index bbe695eaec..c47b9e9140 100644 --- a/docs/reference/hooks/use-organization.mdx +++ b/docs/reference/hooks/use-organization.mdx @@ -8,6 +8,36 @@ sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react- The `useOrganization()` hook retrieves attributes of the currently [active organization](!active-organization). +## Parameters + +`useOrganization()` accepts a single object with the following optional properties: + + + +> [!WARNING] +> By default, the `memberships`, `invitations`, `membershipRequests`, and `domains` attributes aren't populated. To fetch and paginate the data, you must pass `true` or an object with the desired properties. + +### Shared properties + +Optional properties that are shared across the `invitations`, `membershipRequests`, `memberships`, and `domains` properties. + + + + + +> [!NOTE] +> These attributes are updating automatically and will re-render their respective components whenever you set a different organization using the [`setActive({ organization })`](/docs/reference/javascript/clerk#set-active) method or update any of the memberships or invitations. No need for you to manage updating anything manually. + +## Returns + + + +### `PaginatedResources` + + + +To see the different organization features integrated into one application, take a look at our [organizations demo repository](https://github.com/clerk/organizations-demo). + ## Examples ### Expand and paginate attributes @@ -44,7 +74,7 @@ const { invitations } = useOrganization({ 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. - ```jsx {{ filename: 'components/MemberList.tsx' }} + ```jsx {{ filename: 'src/components/MemberList.tsx' }} import { useOrganization } from '@clerk/clerk-react' export default function MemberList() { @@ -55,10 +85,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -85,12 +113,12 @@ The following example demonstrates how to use the `infinite` property to fetch a - ```tsx {{ filename: 'app/users/page.tsx' }} + ```tsx {{ filename: 'app/organization/members/page.tsx' }} 'use client' import { useOrganization } from '@clerk/nextjs' - export default function MemberListPage() { + export default function Page() { const { memberships } = useOrganization({ memberships: { infinite: true, // Append new data to the existing list @@ -98,10 +126,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -139,10 +165,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -180,51 +204,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!memberships) { - // Handle loading state - return null - } - - return ( -
-

Organization members

-
    - {memberships.data?.map((membership) => ( -
  • - {membership.publicUserData?.firstName} {membership.publicUserData?.lastName} < - {membership.publicUserData?.identifier}> :: {membership.role} -
  • - ))} -
- - -
- ) - } - ``` - - - - ```tsx {{ filename: 'app/routes/members.tsx' }} - import { useOrganization } from '@clerk/remix' - - export default function MemberListPage() { - 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 - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -259,7 +240,7 @@ The following example demonstrates how to use the `infinite` property to fetch a component: MemberListPage, }) - function MemberListPage() { + export default function MemberListPage() { const { memberships } = useOrganization({ memberships: { infinite: true, // Append new data to the existing list @@ -267,10 +248,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -309,10 +288,8 @@ The following example demonstrates how to use the `infinite` property to fetch a }, }) - if (!memberships) { - // Handle loading state - return Loading... - } + // Handle loading state + if (!memberships) return Loading... return ( @@ -347,7 +324,7 @@ The following example demonstrates how to use the `fetchPrevious` and `fetchNext Notice the difference between this example's pagination and the infinite pagination example above. - ```jsx {{ filename: 'components/MemberList.tsx' }} + ```jsx {{ filename: 'src/components/MemberList.tsx' }} import { useOrganization } from '@clerk/clerk-react' export default function MemberList() { @@ -357,10 +334,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -388,22 +363,20 @@ Notice the difference between this example's pagination and the infinite paginat - ```tsx {{ filename: 'app/users/page.tsx' }} + ```tsx {{ filename: 'app/members/page.tsx' }} 'use client' import { useOrganization } from '@clerk/nextjs' - export default function MemberListPage() { + export default function Page() { const { memberships } = useOrganization({ memberships: { keepPreviousData: true, // Persist the cached data until the new data has been fetched }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -441,10 +414,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -482,51 +453,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!memberships) { - // Handle loading state - return null - } - - return ( -
-

Organization members

-
    - {memberships.data?.map((membership) => ( -
  • - {membership.publicUserData?.firstName} {membership.publicUserData?.lastName} < - {membership.publicUserData?.identifier}> :: {membership.role} -
  • - ))} -
- - - - -
- ) - } - ``` - - - - ```tsx {{ filename: 'app/routes/members-paginated.tsx' }} - import { useOrganization } from '@clerk/remix' - - export default function MemberListPage() { - const { memberships } = useOrganization({ - memberships: { - keepPreviousData: true, // Persist the cached data until the new data has been fetched - }, - }) - - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -562,17 +490,15 @@ Notice the difference between this example's pagination and the infinite paginat component: MemberListPage, }) - function MemberListPage() { + export default function MemberListPage() { const { memberships } = useOrganization({ memberships: { keepPreviousData: true, // Persist the cached data until the new data has been fetched }, }) - if (!memberships) { - // Handle loading state - return null - } + // Handle loading state + if (!memberships) return
Loading...
return (
@@ -611,10 +537,8 @@ Notice the difference between this example's pagination and the infinite paginat }, }) - if (!memberships) { - // Handle loading state - return Loading... - } + // Handle loading state + if (!memberships) return Loading... return ( @@ -643,32 +567,24 @@ Notice the difference between this example's pagination and the infinite paginat ``` -## Properties +## Related guides -`useOrganization()` accepts a single object with the following optional properties: + + - [Update an organization](/docs/guides/development/custom-flows/organizations/update-organizations) + - Use Clerk's API to build a custom flow for updating an organization - + --- -> [!WARNING] -> By default, the `memberships`, `invitations`, `membershipRequests`, and `domains` attributes aren't populated. To fetch and paginate the data, you must pass `true` or an object with the desired properties. + - [Manage roles in an organization](/docs/guides/development/custom-flows/organizations/manage-roles) + - Use Clerk's API to build a custom flow for managing roles in an organization -### Shared properties + --- -Optional properties that are shared across the `invitations`, `membershipRequests`, `memberships`, and `domains` properties. + - [Manage an organization's membership requests](/docs/guides/development/custom-flows/organizations/manage-membership-requests) + - Use Clerk's API to build a custom flow for managing an organization's membership requests - - - - -> [!NOTE] -> These attributes are updating automatically and will re-render their respective components whenever you set a different organization using the [`setActive({ organization })`](/docs/reference/javascript/clerk#set-active) method or update any of the memberships or invitations. No need for you to manage updating anything manually. - -## Returns + --- - - -### `PaginatedResources` - - - -To see the different organization features integrated into one application, take a look at our [organizations demo repository](https://github.com/clerk/organizations-demo). + - [Manage a user's organization invitations](/docs/guides/development/custom-flows/organizations/manage-user-org-invitations) + - Use Clerk's API to build a custom flow for managing user's organization invitations + diff --git a/docs/reference/hooks/use-payment-attempts.mdx b/docs/reference/hooks/use-payment-attempts.mdx index 968eb44acc..0fcd01a0a1 100644 --- a/docs/reference/hooks/use-payment-attempts.mdx +++ b/docs/reference/hooks/use-payment-attempts.mdx @@ -10,289 +10,298 @@ The `usePaymentAttempts()` hook provides access to the payment attempts associat ## Parameters -`usePaymentAttempts()` accepts a single object with the following properties: +`usePaymentAttempts()` accepts a single optional object with the following properties: - - - `for?` - - `'user' | 'organization'` - - Specifies whether to fetch payment attempts for the current user or organization. Defaults to `'user'`. - - --- - - - `pageSize?` - - `number` - - The number of payment attempts to fetch per page. Defaults to `10`. - - --- - - - `initialPage?` - - `number` - - The page number to start fetching from. Defaults to `1`. - - --- - - - `infinite?` - - `boolean` - - When `true`, enables infinite pagination mode where new pages are appended to existing data. When `false`, each page replaces the previous data. Defaults to `false`. - - --- - - - `keepPreviousData?` - - `boolean` - - When `true`, the previous data will be kept while loading the next page. This helps prevent layout shifts. Defaults to `false`. - + ## Returns `usePaymentAttempts()` returns an object with the following properties: - - - `data` - - [BillingPaymentResource](/docs/reference/javascript/types/billing-payment-resource)\[] | null - - An array of payment attempts, or `null` if the data hasn't been loaded yet. - - --- - - - `isLoading` - - `boolean` - - A boolean that is `true` if there is an ongoing request and there is no fetched data. - - --- - - - `isFetching` - - `boolean` - - A boolean that is `true` if there is an ongoing request or a revalidation. - - --- - - - `hasNextPage` - - `boolean` - - A boolean that indicates if there are available pages to be fetched. - - --- - - - `hasPreviousPage` - - `boolean` - - A boolean that indicates if there are available pages to be fetched. + - --- - - - `fetchNext` - - () => void - - A function that triggers the next page to be loaded. This is the same as `fetchPage(page => Math.min(pageCount, page + 1))`. - - --- - - - `fetchPrevious` - - () => void - - A function that triggers the previous page to be loaded. This is the same as `fetchPage(page => Math.max(0, page - 1))`. - - --- - - - `pageCount` - - `number` - - The total amount of pages. It is calculated based on `count`, `initialPage`, and `pageSize`. - - --- - - - `count` - - `number` - - The total number of payment attempts available. - - --- - - - `error` - - [ClerkAPIResponseError](/docs/reference/javascript/types/clerk-api-response-error) | null - - Returns an error object if any part of the process fails. - - --- +## Examples - - `fetchPage` - - `(page: number) => void` +### Basic usage - A function that triggers a specific page to be loaded. +The following example demonstrates how to fetch and display a user's payment attempts. - --- + + ```tsx {{ filename: 'app/billing/payment-attempts/page.tsx' }} + 'use client' - - `isError` - - `boolean` + import { usePaymentAttempts } from '@clerk/nextjs/experimental' - A boolean that indicates the request failed. + export default function PaymentAttemptsList() { + const { data, isLoading } = usePaymentAttempts({ + for: 'user', + pageSize: 10, + }) - --- + if (isLoading) { + return
Loading payment attempts...
+ } - - `page` - - `number` + if (!data || data.length === 0) { + return
No payment attempts found.
+ } - The current page. + return ( +
    + {data?.map((attempt) => ( +
  • + Payment #{attempt.id} - {attempt.status} +
    + Amount: {attempt.amount.amountFormatted} on {new Date(attempt.updatedAt).toLocaleString()} +
  • + ))} +
+ ) + } + ``` +
- --- + + ```tsx {{ filename: 'src/pages/billing/PaymentAttemptsList.tsx' }} + import { usePaymentAttempts } from '@clerk/clerk-react/experimental' - - `revalidate` - - () => Promise\ + export default function PaymentAttemptsList() { + const { data, isLoading } = usePaymentAttempts({ + for: 'user', + pageSize: 10, + }) - A function that triggers a revalidation of the current page. + if (isLoading) { + return
Loading payment attempts...
+ } - --- + if (!data || data.length === 0) { + return
No payment attempts found.
+ } - - `setData` - - `(data: any[]) => void` + return ( +
    + {data?.map((attempt) => ( +
  • + Payment #{attempt.id} - {attempt.status} +
    + Amount: {attempt.amount.amountFormatted} on {new Date(attempt.updatedAt).toLocaleString()} +
  • + ))} +
+ ) + } + ``` +
- A function that allows you to set the data manually. -
+### Infinite pagination -## Examples +The following example demonstrates how to implement infinite scrolling with payment attempts. -### Basic usage + + ```tsx {{ filename: 'app/billing/payment-attempts/page.tsx' }} + 'use client' -The following example demonstrates how to fetch and display a user's payment attempts. + import { usePaymentAttempts } from '@clerk/nextjs/experimental' -```tsx -import { usePaymentAttempts } from '@clerk/nextjs/experimental' + export default function InfinitePaymentAttempts() { + const { data, isLoading, hasNextPage, fetchNext } = usePaymentAttempts({ + for: 'user', + infinite: true, + pageSize: 20, + }) -function PaymentAttemptsList() { - const { data, isLoading } = usePaymentAttempts({ - for: 'user', - pageSize: 10, - }) + if (isLoading) { + return
Loading...
+ } - if (isLoading) { - return
Loading payment attempts...
- } + if (!data || data.length === 0) { + return
No payment attempts found.
+ } - if (!data || data.length === 0) { - return
No payment attempts found.
+ return ( +
+
    + {data?.map((attempt) => ( +
  • + Payment attempt for {attempt.amount.amountFormatted} +
    + Status: {attempt.status} +
    + {attempt.status === 'failed' && attempt.failedAt && ( + Failed At: {new Date(attempt.failedAt).toLocaleString()} + )} +
  • + ))} +
+ + {hasNextPage && } +
+ ) } + ``` +
+ + + ```tsx {{ filename: 'src/pages/billing/PaymentAttemptsList.tsx' }} + import { usePaymentAttempts } from '@clerk/clerk-react/experimental' + + export default function InfinitePaymentAttempts() { + const { data, isLoading, hasNextPage, fetchNext } = usePaymentAttempts({ + for: 'user', + infinite: true, + pageSize: 20, + }) + + if (isLoading) { + return
Loading...
+ } - return ( -
    - {data?.map((attempt) => ( -
  • - Payment #{attempt.id} - {attempt.status} -
    - Amount: {attempt.amount.amountFormatted} on {new Date(attempt.updatedAt).toLocaleString()} -
  • - ))} -
- ) -} -``` + if (!data || data.length === 0) { + return
No payment attempts found.
+ } -### Infinite pagination + return ( +
+
    + {data?.map((attempt) => ( +
  • + Payment attempt for {attempt.amount.amountFormatted} +
    + Status: {attempt.status} +
    + {attempt.status === 'failed' && attempt.failedAt && ( + Failed At: {new Date(attempt.failedAt).toLocaleString()} + )} +
  • + ))} +
+ + {hasNextPage && } +
+ ) + } + ``` +
-The following example demonstrates how to implement infinite scrolling with payment attempts. +### Payment attempts history table -```tsx -import { usePaymentAttempts } from '@clerk/nextjs/experimental' +The following example demonstrates how to use `usePaymentAttempts()` to display a detailed payment history table. -function InfinitePaymentAttempts() { - const { data, isLoading, hasNextPage, fetchNext } = usePaymentAttempts({ - for: 'user', - infinite: true, - pageSize: 20, - }) + + ```tsx {{ filename: 'app/billing/payment-attempts-history/page.tsx', collapsible: true }} + 'use client' - if (isLoading) { - return
Loading...
- } + import { usePaymentAttempts } from '@clerk/nextjs/experimental' - if (!data || data.length === 0) { - return
No payment attempts found.
- } + export default function PaymentAttemptsHistory() { + const { data, isLoading } = usePaymentAttempts({ for: 'user' }) - return ( -
-
    - {data?.map((attempt) => ( -
  • - Payment attempt for {attempt.amount.amountFormatted} -
    - Status: {attempt.status} -
    - {attempt.status === 'failed' && attempt.failedAt && ( - Failed At: {new Date(attempt.failedAt).toLocaleString()} - )} -
  • - ))} -
+ if (isLoading) { + return
Loading payment attempts...
+ } - {hasNextPage && } -
- ) -} -``` + if (!data || data.length === 0) { + return
No payment attempts found.
+ } -### Payment attempts history table + const getStatusColor = (status: string) => { + switch (status) { + case 'paid': + return 'green' + case 'failed': + return 'red' + case 'pending': + return 'orange' + default: + return 'gray' + } + } -The following example demonstrates how to use `usePaymentAttempts()` to display a detailed payment history table. + return ( + + + + + + + + + + + + {data?.map((attempt) => ( + + + + + + + + ))} + +
Payment IDAmountStatusDatePayment Method
{attempt.id}{attempt.amount.amountFormatted}{attempt.status}{attempt.paidAt ? new Date(attempt.paidAt).toLocaleDateString() : '-'} + {attempt.paymentSource.cardType} ****{attempt.paymentSource.last4} +
+ ) + } + ``` +
-```tsx -import { usePaymentAttempts } from '@clerk/nextjs/experimental' + + ```tsx {{ filename: 'src/pages/billing/PaymentAttemptsHistory.tsx', collapsible: true }} + import { usePaymentAttempts } from '@clerk/clerk-react/experimental' -function PaymentAttemptsHistory() { - const { data, isLoading } = usePaymentAttempts({ for: 'user' }) + export default function PaymentAttemptsHistory() { + const { data, isLoading } = usePaymentAttempts({ for: 'user' }) - if (isLoading) { - return
Loading payment attempts...
- } + if (isLoading) { + return
Loading payment attempts...
+ } - if (!data || data.length === 0) { - return
No payment attempts found.
- } + if (!data || data.length === 0) { + return
No payment attempts found.
+ } - const getStatusColor = (status: string) => { - switch (status) { - case 'paid': - return 'green' - case 'failed': - return 'red' - case 'pending': - return 'orange' - default: - return 'gray' + const getStatusColor = (status: string) => { + switch (status) { + case 'paid': + return 'green' + case 'failed': + return 'red' + case 'pending': + return 'orange' + default: + return 'gray' + } } - } - return ( - - - - - - - - - - - - {data?.map((attempt) => ( - - - - - - + return ( +
Payment IDAmountStatusDatePayment Method
{attempt.id}{attempt.amount.amountFormatted}{attempt.status}{attempt.paidAt ? new Date(attempt.paidAt).toLocaleDateString() : '-'} - {attempt.paymentSource.cardType} ****{attempt.paymentSource.last4} -
+ + + + + + + - ))} - -
Payment IDAmountStatusDatePayment Method
- ) -} -``` + + + {data?.map((attempt) => ( + + {attempt.id} + {attempt.amount.amountFormatted} + {attempt.status} + {attempt.paidAt ? new Date(attempt.paidAt).toLocaleDateString() : '-'} + + {attempt.paymentSource.cardType} ****{attempt.paymentSource.last4} + + + ))} + + + ) + } + ``` +
diff --git a/docs/reference/hooks/use-payment-element.mdx b/docs/reference/hooks/use-payment-element.mdx index 92ceef79e0..47e7c200fa 100644 --- a/docs/reference/hooks/use-payment-element.mdx +++ b/docs/reference/hooks/use-payment-element.mdx @@ -18,40 +18,7 @@ This hook must be used within a component that is a descendant of the ` - - `submit()` - - `() => Promise<{ data: { gateway: 'stripe'; paymentToken: string } | null; error: PaymentElementError | null}>` - - A function that submits the payment form data to the payment provider. It returns a promise that resolves with either a `data` object containing a payment token on success, or an `error` object on failure. - - --- - - - `reset()` - - `() => Promise` - - A function that resets the payment form to its initial, empty state. - - --- - - - `isFormReady` - - `boolean` - - A boolean that indicates if the payment form UI has been rendered and is ready for user input. This is useful for disabling a submit button until the form is interactive. - - --- - - - `isProviderReady` - - `boolean` - - A boolean that indicates if the underlying payment provider (e.g. Stripe) has been fully initialized. - - --- - - - `provider` - - `{ name: 'stripe' } | undefined` - - An object containing information about the initialized payment provider. It is `undefined` until `isProviderReady` is `true`. - + ## Payment element components @@ -63,33 +30,7 @@ The `` component sets up the context for the payment e #### Properties - - - `checkout?` - - [`BillingCheckoutResource`](/docs/reference/javascript/types/billing-checkout-resource) - - An optional checkout resource object. When provided, the payment element is scoped to the specific checkout session. - - --- - - - `for?` - - `'user' | 'organization'` - - Specifies whether the payment method is being added for a user or an organization. Defaults to `'user'`. - - --- - - - `stripeAppearance?` - - `object` - - An optional object to customize the appearance of the Stripe Payment Element. This allows you to match the form's styling to your application's theme. - - --- - - - `paymentDescription?` - - `string` - - An optional description to display to the user within the payment element UI. - + ### `` @@ -97,14 +38,9 @@ This component renders the actual payment form from the provider (e.g., the Stri #### Properties - - - `fallback?` - - `ReactNode` - - Optional fallback content, such as a loading skeleton, to display while the payment form is being initialized. - + -## Examples +## Example diff --git a/docs/reference/hooks/use-payment-methods.mdx b/docs/reference/hooks/use-payment-methods.mdx index e80c8c9d11..9b39a23eac 100644 --- a/docs/reference/hooks/use-payment-methods.mdx +++ b/docs/reference/hooks/use-payment-methods.mdx @@ -10,237 +10,273 @@ The `usePaymentMethods()` hook provides access to the payment methods associated ## Parameters -`usePaymentMethods()` accepts a single object with the following properties: +`usePaymentMethods()` accepts a single optional object with the following properties: - - - `for?` - - `'user' | 'organization'` - - Specifies whether to fetch payment methods for the current user or organization. Defaults to `'user'`. - - --- - - - `pageSize?` - - `number` - - The number of payment methods to fetch per page. Defaults to `10`. - - --- - - - `initialPage?` - - `number` - - The page number to start fetching from. Defaults to `1`. - - --- - - - `infinite?` - - `boolean` - - When `true`, enables infinite pagination mode where new pages are appended to existing data. When `false`, each page replaces the previous data. Defaults to `false`. - - --- - - - `keepPreviousData?` - - `boolean` - - When `true`, the previous data will be kept while loading the next page. This helps prevent layout shifts. Defaults to `false`. - + ## Returns `usePaymentMethods()` returns an object with the following properties: - - - `data` - - [BillingPaymentMethodResource](/docs/reference/javascript/types/billing-payment-method-resource)\[] | null - - An array of [payment methods](/docs/reference/javascript/types/billing-payment-method-resource), or `null` if the data hasn't been loaded yet. + - --- - - - `isLoading` - - `boolean` - - A boolean that indicates whether the initial data is still being fetched. - - --- - - - `isFetching` - - `boolean` - - A boolean that indicates whether any request is still in flight, including background updates. - - --- - - - `hasNextPage` - - `boolean` - - A boolean that indicates whether there are more pages available to load. - - --- - - - `hasPreviousPage` - - `boolean` - - A boolean that indicates whether there are previous pages available to load. - - --- - - - `fetchNext` - - `() => Promise` - - A function to fetch the next page of payment methods. - - --- - - - `fetchPrevious` - - `() => Promise` - - A function to fetch the previous page of payment methods. +## Examples - --- +### Basic usage - - `pageCount` - - `number` +The following example demonstrates how to fetch and display a user's payment methods. - The total number of available pages. + + ```tsx {{ filename: 'app/billing/payment-methods/page.tsx' }} + 'use client' - --- + import { usePaymentMethods } from '@clerk/nextjs/experimental' - - `count` - - `number` + export default function PaymentMethodsList() { + const { data, isLoading } = usePaymentMethods({ + for: 'user', + pageSize: 10, + }) - The total number of payment methods available. - + if (isLoading) { + return
Loading payment methods...
+ } -## Examples + if (!data || data.length === 0) { + // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/add-new-payment-method + return
No payment methods found. Please add a payment method to your account.
+ } -### Basic usage + return ( +
    + {data?.map((method) => ( +
  • + {method.cardType} **** {method.last4} + {method.isDefault ? ' (Default)' : null} +
  • + ))} +
+ ) + } + ``` + -The following example demonstrates how to fetch and display a user's payment methods. + + ```tsx {{ filename: 'src/pages/billing/PaymentMethodsList.tsx' }} + import { usePaymentMethods } from '@clerk/clerk-react/experimental' -```tsx -import { usePaymentMethods } from '@clerk/nextjs/experimental' + export default function PaymentMethodsList() { + const { data, isLoading } = usePaymentMethods({ + for: 'user', + pageSize: 10, + }) -function PaymentMethodsList() { - const { data, isLoading } = usePaymentMethods({ - for: 'user', - pageSize: 10, - }) + if (isLoading) { + return
Loading payment methods...
+ } - if (isLoading) { - return
Loading payment methods...
- } + if (!data || data.length === 0) { + // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/add-new-payment-method + return
No payment methods found. Please add a payment method to your account.
+ } - if (!data || data.length === 0) { - // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/add-new-payment-method - return
No payment methods found. Please add a payment method to your account.
+ return ( +
    + {data?.map((method) => ( +
  • + {method.cardType} **** {method.last4} + {method.isDefault ? ' (Default)' : null} +
  • + ))} +
+ ) } - - return ( -
    - {data?.map((method) => ( -
  • - {method.cardType} **** {method.last4} - {method.isDefault ? ' (Default)' : null} -
  • - ))} -
- ) -} -``` + ``` +
### Infinite pagination The following example demonstrates how to implement infinite scrolling with payment methods. -```tsx -import { usePaymentMethods } from '@clerk/nextjs/experimental' + + ```tsx {{ filename: 'app/billing/payment-methods/page.tsx' }} + 'use client' -function InfinitePaymentMethods() { - const { data, isLoading, hasNextPage, fetchNext } = usePaymentMethods({ - for: 'user', - infinite: true, - pageSize: 20, - }) + import { usePaymentMethods } from '@clerk/nextjs/experimental' - if (isLoading) { - return
Loading...
- } + export default function InfinitePaymentMethods() { + const { data, isLoading, hasNextPage, fetchNext } = usePaymentMethods({ + for: 'user', + infinite: true, + pageSize: 20, + }) + + if (isLoading) { + return
Loading...
+ } - if (!data || data.length === 0) { - // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/add-new-payment-method - return
No payment methods found. Please add a payment method to your account.
+ if (!data || data.length === 0) { + // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/add-new-payment-method + return
No payment methods found. Please add a payment method to your account.
+ } + + return ( +
+
    + {data?.map((method) => ( +
  • + {method.cardType} ending in {method.last4} + {method.status === 'expired' ? ' (Expired)' : null} + {method.status === 'disconnected' ? ' (Disconnected)' : null} +
  • + ))} +
+ + {hasNextPage && } +
+ ) } + ``` +
+ + + ```tsx {{ filename: 'src/pages/billing/PaymentMethodsList.tsx' }} + import { usePaymentMethods } from '@clerk/clerk-react/experimental' + + export default function InfinitePaymentMethods() { + const { data, isLoading, hasNextPage, fetchNext } = usePaymentMethods({ + for: 'user', + infinite: true, + pageSize: 20, + }) + + if (isLoading) { + return
Loading...
+ } - return ( -
-
    - {data?.map((method) => ( -
  • - {method.cardType} ending in {method.last4} - {method.status === 'expired' ? ' (Expired)' : null} - {method.status === 'disconnected' ? ' (Disconnected)' : null} -
  • - ))} -
+ if (!data || data.length === 0) { + // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/add-new-payment-method + return
No payment methods found. Please add a payment method to your account.
+ } - {hasNextPage && } -
- ) -} -``` + return ( +
+
    + {data?.map((method) => ( +
  • + {method.cardType} ending in {method.last4} + {method.status === 'expired' ? ' (Expired)' : null} + {method.status === 'disconnected' ? ' (Disconnected)' : null} +
  • + ))} +
+ + {hasNextPage && } +
+ ) + } + ``` +
### With checkout flow The following example demonstrates how to use `usePaymentMethods()` in a checkout flow to select an existing payment method. For more information on how to build a checkout flow with an existing payment method, see [Build a custom checkout flow](/docs/guides/development/custom-flows/billing/checkout-new-payment-method). -```tsx -import { usePaymentMethods, useCheckout } from '@clerk/nextjs/experimental' -import { useRouter } from 'next/navigation' - -function CheckoutPaymentSelection() { - const { data, isLoading } = usePaymentMethods({ for: 'user' }) - const { checkout } = useCheckout() - const { confirm, finalize } = checkout - - const router = useRouter() - - const handlePaymentSubmit = async (paymentMethodId: string) => { - try { - // Confirm checkout with selected payment method - await confirm({ paymentSourceId: paymentMethodId }) - // Complete checkout and redirect - await finalize({ - navigate: () => router.push('/dashboard'), - }) - } catch (error) { - console.error('Payment failed:', error) + + ```tsx {{ filename: 'app/billing/checkout/page.tsx' }} + 'use client' + + import { usePaymentMethods, useCheckout } from '@clerk/nextjs/experimental' + import { useRouter } from 'next/navigation' + + export default function CheckoutPaymentSelection() { + const { data, isLoading } = usePaymentMethods({ for: 'user' }) + const { checkout } = useCheckout() + const { confirm, finalize } = checkout + const router = useRouter() + + const handlePaymentSubmit = async (paymentMethodId: string) => { + try { + // Confirm checkout with selected payment method + await confirm({ paymentSourceId: paymentMethodId }) + // Complete checkout and redirect + await finalize({ + navigate: () => router.push('/dashboard'), + }) + } catch (error) { + console.error('Payment failed:', error) + } + + if (isLoading) { + return
Loading payment methods...
+ } + + if (!data || data.length === 0) { + // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/checkout-new-payment-method + return
No payment methods found. Please add a payment method to your account.
+ } + + return ( +
+

Select a payment method

+ {data?.map((method) => ( + + ))} +
+ ) } } + ``` +
+ + + ```tsx {{ filename: 'src/pages/billing/CheckoutPaymentSelection.tsx' }} + import { usePaymentMethods, useCheckout } from '@clerk/clerk-react/experimental' + import { useNavigate } from 'react-router-dom' + + export default function CheckoutPaymentSelection() { + const { data, isLoading } = usePaymentMethods({ for: 'user' }) + const { checkout } = useCheckout() + const { confirm, finalize } = checkout + const navigate = useNavigate() + + const handlePaymentSubmit = async (paymentMethodId: string) => { + try { + // Confirm checkout with selected payment method + await confirm({ paymentSourceId: paymentMethodId }) + // Complete checkout and redirect + await finalize({ + navigate: () => navigate('/dashboard'), + }) + } catch (error) { + console.error('Payment failed:', error) + } + } - if (isLoading) { - return
Loading payment methods...
- } + if (isLoading) { + return
Loading payment methods...
+ } - if (!data || data.length === 0) { - // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/checkout-new-payment-method - return
No payment methods found. Please add a payment method to your account.
- } + if (!data || data.length === 0) { + // Code for how to add a new payment method: https://clerk.com/docs/guides/development/custom-flows/billing/checkout-new-payment-method + return
No payment methods found. Please add a payment method to your account.
+ } - return ( -
-

Select a payment method

- {data?.map((method) => ( - - ))} -
- ) -} -``` + return ( +
+

Select a payment method

+ {data?.map((method) => ( + + ))} +
+ ) + } + ``` +
## Related guides diff --git a/docs/reference/hooks/use-plans.mdx b/docs/reference/hooks/use-plans.mdx index 80b2ae763e..e17b93112e 100644 --- a/docs/reference/hooks/use-plans.mdx +++ b/docs/reference/hooks/use-plans.mdx @@ -10,109 +10,15 @@ The `usePlans()` hook provides access to the subscription plans available in you ## Parameters -`usePlans()` accepts a single object with the following properties: +`usePlans()` accepts a single optional object with the following properties: - - - `for?` - - `'user' | 'organization'` - - Specifies whether to fetch plans for users or organizations. Defaults to `'user'`. - - --- - - - `pageSize?` - - `number` - - The number of plans to fetch per page. Defaults to `10`. - - --- - - - `initialPage?` - - `number` - - The page number to start fetching from. Defaults to `1`. - - --- - - - `infinite?` - - `boolean` - - When `true`, enables infinite pagination mode where new pages are appended to existing data. When `false`, each page replaces the previous data. Defaults to `false`. - - --- - - - `keepPreviousData?` - - `boolean` - - When `true`, the previous data will be kept while loading the next page. This helps prevent layout shifts. Defaults to `false`. - + ## Returns `usePlans()` returns an object with the following properties: - - - `data` - - [`BillingPlanResource[]`](/docs/reference/javascript/types/billing-plan-resource) | null - - An array of [plans](/docs/reference/javascript/types/billing-plan-resource), or `null` if the data hasn't been loaded yet. - - --- - - - `isLoading` - - `boolean` - - A boolean that indicates whether the initial data is still being fetched. - - --- - - - `isFetching` - - `boolean` - - A boolean that indicates whether any request is still in flight, including background updates. - - --- - - - `hasNextPage` - - `boolean` - - A boolean that indicates whether there are more pages available to load. - - --- - - - `hasPreviousPage` - - `boolean` - - A boolean that indicates whether there are previous pages available to load. - - --- - - - `fetchNext` - - `() => Promise` - - A function to fetch the next page of plans. - - --- - - - `fetchPrevious` - - `() => Promise` - - A function to fetch the previous page of plans. - - --- - - - `pageCount` - - `number` - - The total number of available pages. - - --- - - - `count` - - `number` - - The total number of plans available. - + ## Examples @@ -120,71 +26,68 @@ The `usePlans()` hook provides access to the subscription plans available in you The following example shows how to fetch and display available plans. -```tsx -'use client' -import { usePlans } from '@clerk/nextjs/experimental' + + ```tsx {{ filename: 'app/billing/plans/page.tsx' }} + 'use client' -function PlansList() { - const { data, isLoading, hasNextPage, fetchNext, hasPreviousPage, fetchPrevious } = usePlans({ - for: 'user', - pageSize: 10, - }) + import { usePlans } from '@clerk/nextjs/experimental' - if (isLoading) { - return
Loading plans...
- } + export default function PlansList() { + const { data, isLoading, hasNextPage, fetchNext, hasPreviousPage, fetchPrevious } = usePlans({ + for: 'user', + pageSize: 10, + }) - return ( -
    - {data?.map((plan) => ( -
  • -

    {plan.name}

    -

    {plan.description}

    -

    Is free plan: {!plan.hasBaseFee ? 'Yes' : 'No'}

    -

    - Price per month: {plan.currency} {plan.amountFormatted} -

    -

    - Price per year: {plan.currency} {plan.annualAmountFormatted} equivalent to{' '} - {plan.currency} {plan.annualMonthlyAmountFormatted} per month -

    -

    Features:

    -
      - {plan.features.map((feature) => ( -
    • {feature.name}
    • - ))} -
    -
  • - ))} - - {hasNextPage && } - {hasPreviousPage && } -
- ) -} -``` + if (isLoading) { + return
Loading plans...
+ } -### Infinite pagination + return ( +
    + {data?.map((plan) => ( +
  • +

    {plan.name}

    +

    {plan.description}

    +

    Is free plan: {!plan.hasBaseFee ? 'Yes' : 'No'}

    +

    + Price per month: {plan.currency} {plan.amountFormatted} +

    +

    + Price per year: {plan.currency} {plan.annualAmountFormatted} equivalent to{' '} + {plan.currency} {plan.annualMonthlyAmountFormatted} per month +

    +

    Features:

    +
      + {plan.features.map((feature) => ( +
    • {feature.name}
    • + ))} +
    +
  • + ))} -The following example demonstrates how to implement infinite scrolling with plans. + {hasNextPage && } + {hasPreviousPage && } +
+ ) + } + ``` +
-```tsx -'use client' -import { usePlans } from '@clerk/nextjs/experimental' + + ```tsx {{ filename: 'src/pages/billing/PlansList.tsx' }} + import { usePlans } from '@clerk/clerk-react/experimental' -function InfinitePlansList() { - const { data, isLoading, hasNextPage, fetchNext } = usePlans({ - for: 'user', - infinite: true, - pageSize: 2, - }) + export default function PlansList() { + const { data, isLoading, hasNextPage, fetchNext, hasPreviousPage, fetchPrevious } = usePlans({ + for: 'user', + pageSize: 10, + }) - if (isLoading) { - return
Loading plans...
- } + if (isLoading) { + return
Loading plans...
+ } - return ( -
+ return (
    {data?.map((plan) => (
  • @@ -206,10 +109,123 @@ function InfinitePlansList() {
))} + + {hasNextPage && } + {hasPreviousPage && } + ) + } + ``` + + +### Infinite pagination + +The following example demonstrates how to implement infinite scrolling with plans. + + + ```tsx {{ filename: 'app/billing/plans/page.tsx' }} + 'use client' + + import { usePlans } from '@clerk/nextjs/experimental' + + export default function InfinitePlansList() { + const { data, isLoading, hasNextPage, fetchNext } = usePlans({ + for: 'user', + infinite: true, + pageSize: 2, + }) + + if (isLoading) { + return
Loading plans...
+ } + + return ( +
+
    + {data?.map((plan) => ( +
  • +

    {plan.name}

    +

    {plan.description}

    +

    Is free plan: {!plan.hasBaseFee ? 'Yes' : 'No'}

    +

    + Price per month: {plan.currency} {plan.amountFormatted} +

    +

    + Price per year: {plan.currency} {plan.annualAmountFormatted} equivalent to{' '} + {plan.currency} {plan.annualMonthlyAmountFormatted} per month +

    +

    Features:

    +
      + {plan.features.map((feature) => ( +
    • {feature.name}
    • + ))} +
    +
  • + ))} +
+ + {hasNextPage && } +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/pages/billing/PlansList.tsx' }} + import { usePlans } from '@clerk/clerk-react/experimental' + + export default function InfinitePlansList() { + const { data, isLoading, hasNextPage, fetchNext } = usePlans({ + for: 'user', + infinite: true, + pageSize: 2, + }) + + if (isLoading) { + return
Loading plans...
+ } + + return ( +
+
    + {data?.map((plan) => ( +
  • +

    {plan.name}

    +

    {plan.description}

    +

    Is free plan: {!plan.hasBaseFee ? 'Yes' : 'No'}

    +

    + Price per month: {plan.currency} {plan.amountFormatted} +

    +

    + Price per year: {plan.currency} {plan.annualAmountFormatted} equivalent to{' '} + {plan.currency} {plan.annualMonthlyAmountFormatted} per month +

    +

    Features:

    +
      + {plan.features.map((feature) => ( +
    • {feature.name}
    • + ))} +
    +
  • + ))} +
+ + {hasNextPage && } +
+ ) + } + ``` +
+ +## Related guides + + + - [Checkout flow with a new payment method](/docs/guides/development/custom-flows/billing/checkout-new-payment-method) + - Prompt users to add a new payment method during checkout + + --- - {hasNextPage && } -
- ) -} -``` + - [Checkout flow for returning users](/docs/guides/development/custom-flows/billing/checkout-existing-payment-method) + - Prompt users to select an existing payment method during checkout + diff --git a/docs/reference/hooks/use-reverification.mdx b/docs/reference/hooks/use-reverification.mdx index 6dfb5ce007..f0ac0ad472 100644 --- a/docs/reference/hooks/use-reverification.mdx +++ b/docs/reference/hooks/use-reverification.mdx @@ -12,6 +12,16 @@ The `useReverification()` hook is used to handle a session's reverification flow When using reverification, a user's credentials are valid for 10 minutes. Once stale, a user will need to reverify their credentials. This time duration can be customized by using the `has()` helper on the server-side. See the [guide on reverification](/docs/guides/secure/reverification) for more information. + + +### `UseReverificationOptions` + + + +### `NeedsReverificationParameters` + + + ## Examples The `useReverification()` hook displays a prebuilt UI when the user needs to reverify their credentials. You can also build a custom UI to handle the reverification process yourself. Use the following tabs to see examples of either option. @@ -237,58 +247,6 @@ The `useReverification()` hook displays a prebuilt UI when the user needs to rev ```
- - ```tsx {{ filename: 'components/UpdateUserEmail.tsx', collapsible: true }} - import { useReverification, useUser } from '@clerk/remix' - import { isClerkRuntimeError, isReverificationCancelledError } from '@clerk/shared' - - export function UpdateUserEmail() { - // Use `useUser()` to get the current user's `User` object - // `User` includes the `update()` method to update the user's primary email address - const { user } = useUser() - - // Use `useReverification()` to enhance the `update()` method - // to handle the reverification process - const changePrimaryEmail = useReverification((emailAddressId: string) => - user?.update({ primaryEmailAddressId: emailAddressId }), - ) - - const handleClick = async (emailAddressId: string) => { - try { - await changePrimaryEmail(emailAddressId) - } catch (e) { - // Handle if user cancels the reverification process - if (isClerkRuntimeError(e) && isReverificationCancelledError(e)) { - console.error('User cancelled reverification', e.code) - } - - // Handle other errors - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error(JSON.stringify(e, null, 2)) - } - } - - return ( -
- Your primary email address is {user?.primaryEmailAddress?.emailAddress} - -
    - {user?.emailAddresses.map((email) => ( -
  • - {email.emailAddress} - {email.id !== user?.primaryEmailAddress?.id && ( - - )} -
  • - ))} -
-
- ) - } - ``` -
- ```tsx {{ filename: 'components/UpdateUserEmail.tsx', collapsible: true }} import { useReverification, useUser } from '@clerk/tanstack-react-start' @@ -1983,50 +1941,6 @@ The `useReverification()` hook displays a prebuilt UI when the user needs to rev -## Properties - - - - `fetcher` - - `Fetcher extends (...args: any[]) => Promise` - - A function that returns a promise. - - --- - - - `options?` - - [`UseReverificationOptions`](#use-reverification-options) - - The optional options object. - - -### `UseReverificationOptions` - - - - `onNeedsReverification?` - - (\{ complete, cancel, level }: [NeedsReverificationParameters](#needs-reverification-parameters)) => void - - Handler for the reverification process. Opts out of using the default UI. Use this to build a custom UI. - - -### `NeedsReverificationParameters` - - - - `complete` - - `() => void` - - Marks the reverification process as complete and retries the original request. - - --- - - - `cancel` - - `() => void` - - Marks the reverification process as cancelled and rejects the original request. - - --- - - - `level` - - `"first_factor" | "second_factor" | "multi_factor" | undefined` +## Related guides - The verification level required for the reverification process. - +See the custom flow guides for examples of how to use the `useReverification()` hook, such as the [Add a phone number to a user's account](/docs/guides/development/custom-flows/account-updates/add-phone) guide. diff --git a/docs/reference/hooks/use-session-list.mdx b/docs/reference/hooks/use-session-list.mdx index 5cc504fb2f..9f67db83e5 100644 --- a/docs/reference/hooks/use-session-list.mdx +++ b/docs/reference/hooks/use-session-list.mdx @@ -1,7 +1,137 @@ --- title: useSessionList() description: Access and manage the current user's session list in your React application with Clerk's useSessionList() hook. -sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +The `useSessionList()` hook returns an array of [`Session`](/docs/reference/javascript/session) objects that have been registered on the client device. + + + +## 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. + + + ```tsx {{ filename: 'src/pages/Home.tsx' }} + import { useSessionList } from '@clerk/clerk-react' + + export default function Home() { + const { isLoaded, sessions } = useSessionList() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return ( +
+

Welcome back. You've been here {sessions.length} times before.

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/home/page.tsx' }} + 'use client' + + import { useSessionList } from '@clerk/nextjs' + + export default function Page() { + const { isLoaded, sessions } = useSessionList() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return ( +
+

Welcome back. You've been here {sessions.length} times before.

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useSessionList } from '@clerk/react-router' + + export default function Home() { + const { isLoaded, sessions } = useSessionList() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return ( +
+

Welcome back. You've been here {sessions.length} times before.

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useSessionList } from '@clerk/chrome-extension' + + export default function Home() { + const { isLoaded, sessions } = useSessionList() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return ( +
+

Welcome back. You've been here {sessions.length} times before.

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useSessionList } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: Home, + }) + + export default function Home() { + const { isLoaded, sessions } = useSessionList() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return ( +
+

Welcome back. You've been here {sessions.length} times before.

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/(session-list)/page.tsx' }} + import { useSessionList } from '@clerk/clerk-expo' + import { Text, View } from 'react-native' + + export default function Home() { + const { isLoaded, sessions } = useSessionList() + + // Handle loading state + if (!isLoaded) return Loading... + + return ( + + Welcome back. You've been here {sessions.length} times before. + + ) + } + ``` + diff --git a/docs/reference/hooks/use-session.mdx b/docs/reference/hooks/use-session.mdx index 93af5b99cc..602b127e19 100644 --- a/docs/reference/hooks/use-session.mdx +++ b/docs/reference/hooks/use-session.mdx @@ -1,7 +1,155 @@ --- title: useSession() description: Access and manage the current user's session in your React application with Clerk's useSession() hook. -sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +The `useSession()` hook provides access to the current user's [`Session`](/docs/reference/javascript/session) object, as well as helpers for setting the active session. + + + +## 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. + + + ```tsx {{ filename: 'src/pages/Home.tsx' }} + import { useSession } from '@clerk/clerk-react' + + export default function Home() { + const { isLoaded, session, isSignedIn } = useSession() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

This session has been active since {session.lastActiveAt.toLocaleString()}

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/home/page.tsx' }} + 'use client' + + import { useSession } from '@clerk/nextjs' + + export default function Page() { + const { isLoaded, session, isSignedIn } = useSession() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

This session has been active since {session.lastActiveAt.toLocaleString()}

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useSession } from '@clerk/react-router' + + export default function Home() { + const { isLoaded, session, isSignedIn } = useSession() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

This session has been active since {session.lastActiveAt.toLocaleString()}

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useSession } from '@clerk/chrome-extension' + + export default function HomePage() { + const { isLoaded, session, isSignedIn } = useSession() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

This session has been active since {session.lastActiveAt.toLocaleString()}

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useSession } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: HomePage, + }) + + function HomePage() { + const { isLoaded, session, isSignedIn } = useSession() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return ( +
+

This session has been active since {session.lastActiveAt.toLocaleString()}

+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'app/(session)/page.tsx' }} + import { useSession } from '@clerk/clerk-expo' + import { Text, View } from 'react-native' + + export default function HomePage() { + const { isLoaded, session, isSignedIn } = useSession() + + // Handle loading state + if (!isLoaded) return Loading... + + // Protect the page from unauthenticated users + if (!isSignedIn) return Sign in to view this page + + return ( + + This session has been active since {session.lastActiveAt.toLocaleString()} + + ) + } + ``` + diff --git a/docs/reference/hooks/use-sign-in.mdx b/docs/reference/hooks/use-sign-in.mdx index bddec03f46..6ae2556094 100644 --- a/docs/reference/hooks/use-sign-in.mdx +++ b/docs/reference/hooks/use-sign-in.mdx @@ -1,7 +1,123 @@ --- title: useSignIn() description: Access and manage the current user's sign-in state in your React application with Clerk's useSignIn() hook. -sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +The `useSignIn()` hook provides access to the [`SignIn`](/docs/reference/javascript/sign-in) object, which allows you to check the current state of a sign-in attempt and manage the sign-in flow. You can use this to create a [custom sign-in flow](/docs/guides/development/custom-flows/overview#sign-in-flow). + + + +## Examples + +### Check the current state of a sign-in + +The following example uses the `useSignIn()` hook to access the [`SignIn`](/docs/reference/javascript/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state. + + + ```tsx {{ filename: 'src/pages/SignInPage.tsx' }} + import { useSignIn } from '@clerk/clerk-react' + + export default function SignInPage() { + const { isLoaded, signIn } = useSignIn() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-in attempt status is {signIn?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/sign-in/page.tsx' }} + 'use client' + + import { useSignIn } from '@clerk/nextjs' + + export default function Page() { + const { isLoaded, signIn } = useSignIn() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-in attempt status is {signIn?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/routes/sign-in.tsx' }} + import { useSignIn } from '@clerk/react-router' + + export default function SignInPage() { + const { isLoaded, signIn } = useSignIn() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-in attempt status is {signIn?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useSignIn } from '@clerk/chrome-extension' + + export default function SignInPage() { + const { isLoaded, signIn } = useSignIn() + + if (!isLoaded) { + // Handle loading state + return null + } + + return
The current sign-in attempt status is {signIn?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useSignIn } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: SignInPage, + }) + + export default function SignInPage() { + const { isLoaded, signIn } = useSignIn() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-in attempt status is {signIn?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/(auth)/sign-in.tsx' }} + import { useSignIn } from '@clerk/clerk-expo' + import { Text, View } from 'react-native' + + export default function SignInPage() { + const { isLoaded, signIn } = useSignIn() + + // Handle loading state + if (!isLoaded) return Loading... + + return ( + + The current sign-in attempt status is {signIn?.status}. + + ) + } + ``` + + +### Create a custom sign-in flow with `useSignIn()` + +The `useSignIn()` hook can also be used to build fully custom sign-in flows, if Clerk's prebuilt components don't meet your specific needs or if you require more control over the authentication flow. Different sign-in flows include email and password, email and phone codes, email links, and multifactor (MFA). To learn more about using the `useSignIn()` hook to create custom flows, see the [custom flow guides](/docs/guides/development/custom-flows/overview). diff --git a/docs/reference/hooks/use-sign-up.mdx b/docs/reference/hooks/use-sign-up.mdx index e9d38edece..94d1479d24 100644 --- a/docs/reference/hooks/use-sign-up.mdx +++ b/docs/reference/hooks/use-sign-up.mdx @@ -1,7 +1,121 @@ --- title: useSignUp() description: Access and manage the current user's sign-up state in your React application with Clerk's useSignUp() hook. -sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +The `useSignUp()` hook provides access to the [`SignUp`](/docs/reference/javascript/sign-up) object, which allows you to check the current state of a sign-up attempt and manage the sign-up flow. You can use this to create a [custom sign-up flow](/docs/guides/development/custom-flows/overview#sign-up-flow). + + + +## Examples + +### Check the current state of a sign-up + +The following example uses the `useSignUp()` hook to access the [`SignUp`](/docs/reference/javascript/sign-up) object, which contains the current sign-up attempt status and methods to create a new sign-up attempt. The `isLoaded` property is used to handle the loading state. + + + ```tsx {{ filename: 'src/pages/SignUpPage.tsx' }} + import { useSignUp } from '@clerk/clerk-react' + + export default function SignUpPage() { + const { isLoaded, signUp } = useSignUp() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-up attempt status is {signUp?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/sign-up/page.tsx' }} + 'use client' + + import { useSignUp } from '@clerk/nextjs' + + export default function Page() { + const { isLoaded, signUp } = useSignUp() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-up attempt status is {signUp?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/routes/sign-up.tsx' }} + import { useSignUp } from '@clerk/react-router' + + export default function SignUpPage() { + const { isLoaded, signUp } = useSignUp() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-up attempt status is {signUp?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useSignUp } from '@clerk/chrome-extension' + + export default function SignUpPage() { + const { isLoaded, signUp } = useSignUp() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + return
The current sign-up attempt status is {signUp?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useSignUp } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: SignUpPage, + }) + + export default function SignUpPage() { + const { isLoaded, signUp } = useSignUp() + + // Handle loading state + if (!isLoaded) return Loading... + + return
The current sign-up attempt status is {signUp?.status}.
+ } + ``` +
+ + + ```tsx {{ filename: 'app/(auth)/sign-up.tsx' }} + import { useSignUp } from '@clerk/clerk-expo' + import { Text, View } from 'react-native' + + export default function SignUpPage() { + const { isLoaded, signUp } = useSignUp() + + // Handle loading state + if (!isLoaded) return Loading... + + return ( + + The current sign-up attempt status is {signUp?.status}. + + ) + } + ``` + + +### Create a custom sign-up flow with `useSignUp()` + +The `useSignUp()` hook can also be used to build fully custom sign-up flows, if Clerk's prebuilt components don't meet your specific needs or if you require more control over the authentication flow. Different sign-up flows include email and password, email and phone codes, email links, and multifactor (MFA). To learn more about using the `useSignUp()` hook to create custom flows, see the [custom flow guides](/docs/guides/development/custom-flows/overview). diff --git a/docs/reference/hooks/use-statements.mdx b/docs/reference/hooks/use-statements.mdx index 679f736a4c..f6629081f6 100644 --- a/docs/reference/hooks/use-statements.mdx +++ b/docs/reference/hooks/use-statements.mdx @@ -10,151 +10,15 @@ The `useStatements()` hook provides access to the statements associated with a u ## Parameters -`useStatements()` accepts a single object with the following properties: +`useStatements()` accepts a single optional object with the following properties: - - - `for?` - - `'user' | 'organization'` - - Specifies whether to fetch statements for the current user or organization. Defaults to `'user'`. - - --- - - - `pageSize?` - - `number` - - The number of statements to fetch per page. Defaults to `10`. - - --- - - - `initialPage?` - - `number` - - The page number to start fetching from. Defaults to `1`. - - --- - - - `infinite?` - - `boolean` - - When `true`, enables infinite pagination mode where new pages are appended to existing data. When `false`, each page replaces the previous data. Defaults to `false`. - - --- - - - `keepPreviousData?` - - `boolean` - - When `true`, the previous data will be kept while loading the next page. This helps prevent layout shifts. Defaults to `false`. - + ## Returns `useStatements()` returns an object with the following properties: - - - `data` - - [BillingStatementResource](/docs/reference/javascript/types/billing-statement-resource)\[] | null - - An array of statements, or `null` if the data hasn't been loaded yet. - - --- - - - `isLoading` - - `boolean` - - A boolean that is `true` if there is an ongoing request and there is no fetched data. - - --- - - - `isFetching` - - `boolean` - - A boolean that is `true` if there is an ongoing request or a revalidation. - - --- - - - `hasNextPage` - - `boolean` - - A boolean that indicates if there are available pages to be fetched. - - --- - - - `hasPreviousPage` - - `boolean` - - A boolean that indicates if there are available pages to be fetched. - - --- - - - `fetchNext` - - () => void - - A function that triggers the next page to be loaded. This is the same as `fetchPage(page => Math.min(pageCount, page + 1))`. - - --- - - - `fetchPrevious` - - () => void - - A function that triggers the previous page to be loaded. This is the same as `fetchPage(page => Math.max(0, page - 1))`. - - --- - - - `pageCount` - - `number` - - The total amount of pages. It is calculated based on `count`, `initialPage`, and `pageSize`. - - --- - - - `count` - - `number` - - The total number of statements available. - - --- - - - `error` - - [ClerkAPIResponseError](/docs/reference/javascript/types/clerk-api-response-error) | null - - Returns an error object if any part of the process fails. - - --- - - - `fetchPage` - - `(page: number) => void` - - A function that triggers a specific page to be loaded. - - --- - - - `isError` - - `boolean` - - A boolean that indicates the request failed. - - --- - - - `page` - - `number` - - The current page. - - --- - - - `revalidate` - - () => Promise\ - - A function that triggers a revalidation of the current page. - - --- - - - `setData` - - `(data: any[]) => void` - - A function that allows you to set the data manually. - + ## Examples @@ -162,75 +26,156 @@ The `useStatements()` hook provides access to the statements associated with a u The following example demonstrates how to fetch and display a user's statements. -```tsx -import { useStatements } from '@clerk/nextjs/experimental' + + ```tsx {{ filename: 'app/billing/statements/page.tsx' }} + 'use client' -function StatementsList() { - const { data, isLoading } = useStatements({ - for: 'user', - pageSize: 10, - }) + import { useStatements } from '@clerk/nextjs/experimental' - if (isLoading) { - return
Loading statements...
- } + export default function StatementsList() { + const { data, isLoading } = useStatements({ + for: 'user', + pageSize: 10, + }) - if (!data || data.length === 0) { - return
No statements found.
- } + if (isLoading) { + return
Loading statements...
+ } - return ( -
    - {data?.map((statement) => ( -
  • - Statement ID: {statement.id} - {statement.status} -
    - Date: {statement.timestamp.toLocaleDateString()} -
  • - ))} -
- ) -} -``` - -### Infinite pagination + if (!data || data.length === 0) { + return
No statements found.
+ } -The following example demonstrates how to implement infinite scrolling with statements. + return ( +
    + {data?.map((statement) => ( +
  • + Statement ID: {statement.id} - {statement.status} +
    + Date: {statement.timestamp.toLocaleDateString()} +
  • + ))} +
+ ) + } + ``` +
-```tsx -import { useStatements } from '@clerk/nextjs/experimental' + + ```tsx {{ filename: 'src/pages/billing/StatementsList.tsx' }} + import { useStatements } from '@clerk/clerk-react/experimental' -function InfiniteStatements() { - const { data, isLoading, hasNextPage, fetchNext } = useStatements({ - for: 'user', - infinite: true, - pageSize: 20, - }) + export default function StatementsList() { + const { data, isLoading } = useStatements({ + for: 'user', + pageSize: 10, + }) - if (isLoading) { - return
Loading...
- } + if (isLoading) { + return
Loading statements...
+ } - if (!data || data.length === 0) { - return
No statements found.
- } + if (!data || data.length === 0) { + return
No statements found.
+ } - return ( -
+ return (
    {data?.map((statement) => (
  • - Statement ID: {statement.id} -
    - Amount: {statement.totals.grandTotal.amountFormatted} + Statement ID: {statement.id} - {statement.status}
    - Status: {statement.status} + Date: {statement.timestamp.toLocaleDateString()}
  • ))}
+ ) + } + ``` + + +### Infinite pagination - {hasNextPage && } -
- ) -} -``` +The following example demonstrates how to implement infinite scrolling with statements. + + + ```tsx {{ filename: 'app/billing/statements/page.tsx' }} + 'use client' + + import { useStatements } from '@clerk/nextjs/experimental' + + export default function InfiniteStatements() { + const { data, isLoading, hasNextPage, fetchNext } = useStatements({ + for: 'user', + infinite: true, + pageSize: 20, + }) + + if (isLoading) { + return
Loading...
+ } + + if (!data || data.length === 0) { + return
No statements found.
+ } + + return ( +
+
    + {data?.map((statement) => ( +
  • + Statement ID: {statement.id} +
    + Amount: {statement.totals.grandTotal.amountFormatted} +
    + Status: {statement.status} +
  • + ))} +
+ + {hasNextPage && } +
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/pages/billing/StatementsList.tsx' }} + import { useStatements } from '@clerk/clerk-react/experimental' + + export default function InfiniteStatements() { + const { data, isLoading, hasNextPage, fetchNext } = useStatements({ + for: 'user', + infinite: true, + pageSize: 20, + }) + + if (isLoading) { + return
Loading...
+ } + + if (!data || data.length === 0) { + return
No statements found.
+ } + + return ( +
+
    + {data?.map((statement) => ( +
  • + Statement ID: {statement.id} +
    + Amount: {statement.totals.grandTotal.amountFormatted} +
    + Status: {statement.status} +
  • + ))} +
+ + {hasNextPage && } +
+ ) + } + ``` +
diff --git a/docs/reference/hooks/use-subscription.mdx b/docs/reference/hooks/use-subscription.mdx index fef72ab651..bee6c67a3a 100644 --- a/docs/reference/hooks/use-subscription.mdx +++ b/docs/reference/hooks/use-subscription.mdx @@ -15,203 +15,304 @@ The `useSubscription()` hook provides access to subscription information for use `useSubscription()` accepts a single optional object with the following properties: - - - `for?` - - `'organization' | 'user'` + - Specifies whether to fetch subscription for an organization or user. Defaults to `'user'`. +## Returns - --- +`useSubscription()` returns an object with the following properties: - - `keepPreviousData?` - - `boolean` + - When `true`, the previous data will be kept in the cache until new data is fetched. This helps prevent layout shifts. Defaults to `false`. - +## Examples -## Returns +### Basic usage -`useSubscription()` returns an object with the following properties: +The following example shows how to fetch and display subscription information. - - - `data` - - [`BillingSubscriptionResource`](/docs/reference/javascript/types/billing-subscription-resource) | null + + ```tsx {{ filename: 'app/pricing/subscription-details/page.tsx' }} + 'use client' - The subscription object, or `null` if the data hasn't been loaded yet. + import { useSubscription } from '@clerk/nextjs/experimental' - --- + export default function SubscriptionInfo() { + const { data, isLoading, error } = useSubscription() - - `isLoading` - - `boolean` + if (isLoading) { + return
Loading subscription...
+ } - A boolean that indicates whether the initial data is still being fetched. + if (error) { + return
Error loading subscription: {error.message}
+ } - --- + if (!data) { + return
No subscription found
+ } - - `isFetching` - - `boolean` + return ( +
+

Your Subscription

+ {/* Display subscription details */} +
+ ) + } + ``` +
- A boolean that indicates whether any request is still in flight, including background updates. + + ```tsx {{ filename: 'src/pages/pricing/SubscriptionDetails.tsx' }} + import { useSubscription } from '@clerk/clerk-react/experimental' - --- + export default function SubscriptionInfo() { + const { data, isLoading, error } = useSubscription() - - `error` - - `Error | null` + if (isLoading) { + return
Loading subscription...
+ } - Any error that occurred during the data fetch, or `null` if no error occurred. + if (error) { + return
Error loading subscription: {error.message}
+ } - --- + if (!data) { + return
No subscription found
+ } - - `revalidate` - - `() => Promise` + return ( +
+

Your Subscription

+ {/* Display subscription details */} +
+ ) + } + ``` +
- Function to manually trigger a refresh of the subscription data. -
+### Organization subscription -## Examples +The following example shows how to fetch an organization's subscription. -### Basic usage + + ```tsx {{ filename: 'app/pricing/organization-subscription-details/page.tsx' }} + 'use client' -The following example shows how to fetch and display subscription information. + import { useSubscription } from '@clerk/nextjs/experimental' -```tsx -'use client' -import { useSubscription } from '@clerk/nextjs/experimental' + export default function OrganizationSubscription() { + const { data, isLoading, revalidate } = useSubscription({ + for: 'organization', + keepPreviousData: true, + }) -function SubscriptionInfo() { - const { data, isLoading, error } = useSubscription() + const handleSubscriptionUpdate = async () => { + // After making changes to the subscription + await revalidate() + } - if (isLoading) { - return
Loading subscription...
- } + if (isLoading) { + return
Loading organization subscription...
+ } - if (error) { - return
Error loading subscription: {error.message}
+ return ( +
+

Organization Subscription

+ {/* Display organization subscription details */} + +
+ ) } + ``` +
- if (!data) { - return
No subscription found
- } + + ```tsx {{ filename: 'src/pages/pricing/OrganizationSubscription.tsx' }} + import { useSubscription } from '@clerk/clerk-react/experimental' - return ( -
-

Your Subscription

- {/* Display subscription details */} -
- ) -} -``` + export default function OrganizationSubscription() { + const { data, isLoading, revalidate } = useSubscription({ + for: 'organization', + keepPreviousData: true, + }) -### Organization subscription + const handleSubscriptionUpdate = async () => { + // After making changes to the subscription + await revalidate() + } -The following example shows how to fetch an organization's subscription. + if (isLoading) { + return
Loading organization subscription...
+ } -```tsx -'use client' -import { useSubscription } from '@clerk/nextjs/experimental' + return ( +
+

Organization Subscription

+ {/* Display organization subscription details */} + +
+ ) + } + ``` +
-function OrganizationSubscription() { - const { data, isLoading, revalidate } = useSubscription({ - for: 'organization', - keepPreviousData: true, - }) +### With error handling - const handleSubscriptionUpdate = async () => { - // After making changes to the subscription - await revalidate() - } +The following example shows how to handle subscription data with proper error states. - if (isLoading) { - return
Loading organization subscription...
- } + + ```tsx {{ filename: 'app/pricing/subscription-details/page.tsx', collapsible: true }} + 'use client' - return ( -
-

Organization Subscription

- {/* Display organization subscription details */} - -
- ) -} -``` + import { useSubscription } from '@clerk/nextjs/experimental' -### With error handling + export function SubscriptionDetails() { + const { data: subscription, isLoading } = useSubscription() -The following example shows how to handle subscription data with proper error states. + if (isLoading) { + return
Loading subscription...
+ } -```tsx {{ filename: 'app/pricing/subscription-details/page.tsx' }} -'use client' -import { useSubscription } from '@clerk/nextjs/experimental' + if (!subscription) { + return
No subscription
+ } -function SubscriptionDetails() { - const { data: subscription, isLoading } = useSubscription() + return ( +
+

Subscription Details

+
+ Status: {subscription.status} +
- if (isLoading) { - return
Loading subscription...
- } +
+

Active since: {subscription.activeAt.toLocaleDateString()}

+ {subscription.pastDueAt && ( +

Past due since: {subscription.pastDueAt.toLocaleDateString()}

+ )} +
- if (!subscription) { - return
No subscription
- } + {subscription.nextPayment && ( +
+

Next Payment

+

Amount: {subscription.nextPayment.amount.amountFormatted}

+

Due: {subscription.nextPayment.date.toLocaleDateString()}

+
+ )} - return ( -
-

Subscription Details

-
- Status: {subscription.status} +
+

Subscription Items

+
    + {subscription.subscriptionItems.map((item) => ( +
  • {/* Display subscription item details */}
  • + ))} +
+
+ ) + } + + export default function Page() { + const { data, isLoading, error, isFetching, revalidate } = useSubscription() + + if (error) { + return ( +
+

Failed to load subscription

+

{error.message}

+ +
+ ) + } -
-

Active since: {subscription.activeAt.toLocaleDateString()}

- {subscription.pastDueAt && ( -

Past due since: {subscription.pastDueAt.toLocaleDateString()}

+ return ( +
+ {isLoading ? ( +
Loading...
+ ) : ( + <> +
{isFetching && Refreshing...}
+ {data ? :
No active subscription
} + )}
+ ) + } + ``` + - {subscription.nextPayment && ( -
-

Next Payment

-

Amount: {subscription.nextPayment.amount.amountFormatted}

-

Due: {subscription.nextPayment.date.toLocaleDateString()}

+ + ```tsx {{ filename: 'src/pages/pricing/SubscriptionDetails.tsx', collapsible: true }} + import { useSubscription } from '@clerk/clerk-react/experimental' + + export function SubscriptionDetails() { + const { data: subscription, isLoading } = useSubscription() + + if (isLoading) { + return
Loading subscription...
+ } + + if (!subscription) { + return
No subscription
+ } + + return ( +
+

Subscription Details

+
+ Status: {subscription.status} +
+ +
+

Active since: {subscription.activeAt.toLocaleDateString()}

+ {subscription.pastDueAt && ( +

Past due since: {subscription.pastDueAt.toLocaleDateString()}

+ )} +
+ + {subscription.nextPayment && ( +
+

Next Payment

+

Amount: {subscription.nextPayment.amount.amountFormatted}

+

Due: {subscription.nextPayment.date.toLocaleDateString()}

+
+ )} + +
+

Subscription Items

+
    + {subscription.subscriptionItems.map((item) => ( +
  • {/* Display subscription item details */}
  • + ))} +
- )} - -
-

Subscription Items

-
    - {subscription.subscriptionItems.map((item) => ( -
  • {/* Display subscription item details */}
  • - ))} -
-
- ) -} + ) + } -export default function Page() { - const { data, isLoading, error, isFetching, revalidate } = useSubscription() + export default function Page() { + const { data, isLoading, error, isFetching, revalidate } = useSubscription() + + if (error) { + return ( +
+

Failed to load subscription

+

{error.message}

+ +
+ ) + } - if (error) { return ( -
-

Failed to load subscription

-

{error.message}

- +
+ {isLoading ? ( +
Loading...
+ ) : ( + <> +
{isFetching && Refreshing...}
+ {data ? :
No active subscription
} + + )}
) } - - return ( -
- {isLoading ? ( -
Loading...
- ) : ( - <> -
{isFetching && Refreshing...}
- {data ? :
No active subscription
} - - )} -
- ) -} -``` + ``` + diff --git a/docs/reference/hooks/use-user.mdx b/docs/reference/hooks/use-user.mdx index 2322c9d0bd..9db56ca3ca 100644 --- a/docs/reference/hooks/use-user.mdx +++ b/docs/reference/hooks/use-user.mdx @@ -1,7 +1,601 @@ --- title: useUser() description: Access and manage the current user's data in your React application with Clerk's useUser() hook. -sdk: chrome-extension, expo, nextjs, react, react-router, remix, tanstack-react-start +sdk: chrome-extension, expo, nextjs, react, react-router, tanstack-react-start --- - +The `useUser()` hook provides access to the current user's [`User`](/docs/reference/javascript/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 and initialized. + + + +## Examples + +### Get the current user + +The following example uses the `useUser()` hook to access the [`User`](/docs/reference/javascript/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/pages/Example.tsx' }} + import { useUser } from '@clerk/clerk-react' + + export default function Example() { + const { isSignedIn, user, isLoaded } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return
Hello {user.firstName}!
+ } + ``` +
+ + + ```tsx {{ filename: 'app/page.tsx' }} + 'use client' + + import { useUser } from '@clerk/nextjs' + + export default function Page() { + const { isSignedIn, user, isLoaded } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return
Hello {user.firstName}!
+ } + ``` +
+ + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useUser } from '@clerk/react-router' + + export default function Home() { + const { isSignedIn, user, isLoaded } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return
Hello {user.firstName}!
+ } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useUser } from '@clerk/chrome-extension' + + export default function Example() { + const { isSignedIn, user, isLoaded } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return
Hello {user.firstName}!
+ } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useUser } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: Home, + }) + + export default function Home() { + const { isSignedIn, user, isLoaded } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + return
Hello {user.firstName}!
+ } + ``` +
+ + + ```tsx {{ filename: 'app/(user)/index.tsx' }} + import { useUser } from '@clerk/clerk-expo' + import { Text, View } from 'react-native' + + export default function Page() { + const { isSignedIn, user, isLoaded } = useUser() + + // Handle loading state + if (!isLoaded) return Loading... + + // Protect the page from unauthenticated users + if (!isSignedIn) return Sign in to view this page + + return ( + + Hello {user.firstName}! + + ) + } + ``` + + +### Update user data + +The following example uses the `useUser()` hook to access the [`User`](/docs/reference/javascript/user) object, which calls the [`update()`](/docs/reference/javascript/user#update) method to update the current user's information. + + + ```tsx {{ filename: 'src/pages/Example.tsx' }} + import { useUser } from '@clerk/clerk-react' + + export default function Example() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + await user.update({ + firstName: 'John', + lastName: 'Doe', + }) + } + + return ( + <> + +

user.firstName: {user.firstName}

+

user.lastName: {user.lastName}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/page.tsx' }} + 'use client' + + import { useUser } from '@clerk/nextjs' + + export default function Page() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + await user.update({ + firstName: 'John', + lastName: 'Doe', + }) + } + + return ( + <> + +

user.firstName: {user.firstName}

+

user.lastName: {user.lastName}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useUser } from '@clerk/react-router' + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + await user.update({ + firstName: 'John', + lastName: 'Doe', + }) + } + + return ( + <> + +

user.firstName: {user.firstName}

+

user.lastName: {user.lastName}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useUser } from '@clerk/chrome-extension' + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + await user.update({ + firstName: 'John', + lastName: 'Doe', + }) + } + + return ( + <> + +

user.firstName: {user.firstName}

+

user.lastName: {user.lastName}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useUser } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: Home, + }) + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + await user.update({ + firstName: 'John', + lastName: 'Doe', + }) + } + + return ( + <> + +

user.firstName: {user.firstName}

+

user.lastName: {user.lastName}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/(user)/index.tsx' }} + import { useUser } from '@clerk/clerk-expo' + import { Text, View, TouchableOpacity } from 'react-native' + + export default function Page() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return Loading... + + // Protect the page from unauthenticated users + if (!isSignedIn) return Sign in to view this page + + const updateUser = async () => { + await user.update({ + firstName: 'John', + lastName: 'Doe', + }) + } + + return ( + + + Update your name + + user.firstName: {user.firstName} + user.lastName: {user.lastName} + + ) + } + ``` + + +### Reload user data + +The following example uses the `useUser()` hook to access the [`User`](/docs/reference/javascript/user) object, which calls the [`reload()`](/docs/reference/javascript/user#reload) method to get the latest user's information. + + + ```tsx {{ filename: 'src/pages/Home.tsx' }} + import { useUser } from '@clerk/clerk-react' + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + // Update data via an API endpoint + const updateMetadata = await fetch('/api/updateMetadata', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 ( + <> + +

user role: {user.publicMetadata.role}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/page.tsx' }} + 'use client' + + import { useUser } from '@clerk/nextjs' + + export default function Page() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + // Update data via an API endpoint + const updateMetadata = await fetch('/api/updateMetadata', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 ( + <> + +

user role: {user.publicMetadata.role}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/home.tsx' }} + import { useUser } from '@clerk/react-router' + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + // Update data via an API endpoint + const updateMetadata = await fetch('/api/updateMetadata', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 ( + <> + +

user role: {user.publicMetadata.role}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'src/routes/page.tsx' }} + import { useUser } from '@clerk/chrome-extension' + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + // Update data via an API endpoint + const updateMetadata = await fetch('/api/updateMetadata', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 ( + <> + +

user role: {user.publicMetadata.role}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/index.tsx' }} + import { useUser } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/')({ + component: Home, + }) + + export default function Home() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return
Loading...
+ + // Protect the page from unauthenticated users + if (!isSignedIn) return
Sign in to view this page
+ + const updateUser = async () => { + // Update data via an API endpoint + const updateMetadata = await fetch('/api/updateMetadata', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 ( + <> + +

user role: {user.publicMetadata.role}

+ + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/(user)/index.tsx' }} + import { useUser } from '@clerk/clerk-expo' + import { Text, View, TouchableOpacity } from 'react-native' + + export default function Page() { + const { isSignedIn, isLoaded, user } = useUser() + + // Handle loading state + if (!isLoaded) return Loading... + + // Protect the page from unauthenticated users + if (!isSignedIn) return Sign in to view this page + + const updateUser = async () => { + // Update data via an API endpoint + const updateMetadata = await fetch('/api/updateMetadata', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 ( + + + Update your metadata + + user role: {user.publicMetadata.role} + + ) + } + ``` +