Hooks
React hooks for billing data, entitlements, and checkout
All hooks must be used inside a <BillingProvider>. They handle loading states, errors, and cleanup automatically.
import {
useCan,
useUsage,
useCredits,
usePlan,
usePlans,
useCheckout,
useSubscribe,
useNozleClient,
useBillingContext,
} from '@nozle-js/react';useCan
Check whether a customer is entitled to use a feature. Fetches the initial state via the API, then subscribes to a WebSocket channel for live updates -- no polling.
Signature
function useCan(customerId: string, featureKey: string): CanStateReturn type
interface CanState {
allowed: boolean;
remaining: number | null;
limit: number | null;
loading: boolean;
error: string | null;
}How it works
- Calls
client.can(customerId, featureKey)on mount to get the initial entitlement state. - Opens a WebSocket subscription on
workspace:\{workspaceId\}:entitlements. - When a publication arrives for the matching customer and feature, state updates instantly.
Real-time updates require workspaceId and centrifugoUrl (WebSocket URL) to be set on BillingProvider. Without them, the hook still works but only returns the initial API result.
Example
import { useCan } from '@nozle-js/react';
function ApiCallButton({ customerId }: { customerId: string }) {
const { allowed, remaining, limit, loading, error } = useCan(
customerId,
'api_calls'
);
if (loading) return <p>Checking access...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<button disabled={!allowed}>Make API Call</button>
{remaining !== null && (
<p>
{remaining} / {limit} calls remaining
</p>
)}
</div>
);
}useUsage
Fetch the current usage value for a specific metric.
Signature
function useUsage(customerId: string, metricKey: string): UsageStateReturn type
interface UsageState {
value: number | null;
loading: boolean;
error: string | null;
}How it works
Calls GET /api/v1/usage/:customerId/:metricKey on mount. The request includes a timeout (default 5s) and Authorization header from the provider's API key.
Example
import { useUsage } from '@nozle-js/react';
function TokenCounter({ customerId }: { customerId: string }) {
const { value, loading, error } = useUsage(customerId, 'tokens_used');
if (loading) return <span>Loading usage...</span>;
if (error) return <span>Failed to load usage</span>;
return (
<div>
<strong>{value?.toLocaleString()}</strong> tokens used this period
</div>
);
}useCredits
Fetch the current credit balance for a customer.
Signature
function useCredits(customerId: string): CreditsStateReturn type
interface CreditsState {
balance: number | null;
loading: boolean;
error: string | null;
}How it works
Calls GET /api/v1/credits/:customerId/balance on mount. Returns the customer's prepaid credit balance.
Example
import { useCredits } from '@nozle-js/react';
function CreditDisplay({ customerId }: { customerId: string }) {
const { balance, loading, error } = useCredits(customerId);
if (loading) return <p>Loading balance...</p>;
if (error) return <p>Could not load credits</p>;
return (
<p>
Credit balance: <strong>${(balance ?? 0).toFixed(2)}</strong>
</p>
);
}usePlan
Return the current plan and subscription status from the entitlements store. This hook uses useSyncExternalStore for tear-free reads.
Signature
function usePlan(): UsePlanResultReturn type
interface UsePlanResult {
data: {
plan_slug: string;
subscription_status: string;
} | null;
isLoading: boolean;
error: Error | null;
}How it works
Reads from the BillingStore snapshot. The store is populated when any entitlement check (useCan, fetchUsage) runs, so usePlan reflects the most recent entitlement data without an extra network call.
Example
import { usePlan } from '@nozle-js/react';
function PlanIndicator() {
const { data, isLoading, error } = usePlan();
if (isLoading) return <span>Loading plan...</span>;
if (error) return <span>Error loading plan</span>;
if (!data) return <span>No active plan</span>;
return (
<span>
Plan: <strong>{data.plan_slug}</strong> ({data.subscription_status})
</span>
);
}usePlans
Fetch all available plans from the API.
Signature
function usePlans(): { plans: Plan[]; isLoading: boolean }Return type
interface Plan {
code: string;
name: string;
amount_cents: number;
amount_currency: string;
interval: string;
}How it works
Calls GET /v1/plans via the store on mount. Returns the full list of plans configured in your Nozle workspace.
Example
import { usePlans } from '@nozle-js/react';
function PlanSelector() {
const { plans, isLoading } = usePlans();
if (isLoading) return <p>Loading plans...</p>;
return (
<ul>
{plans.map((plan) => (
<li key={plan.code}>
{plan.name} --{' '}
{(plan.amount_cents / 100).toFixed(2)} {plan.amount_currency}/
{plan.interval}
</li>
))}
</ul>
);
}useCheckout
Access Stripe payment confirmation from inside a <Checkout> component.
useCheckout must be called inside a <Checkout> component. It relies on Stripe Elements context that <Checkout> provides.
Signature
function useCheckout(): UseCheckoutResultReturn type
interface UseCheckoutResult {
/** Trigger Stripe payment confirmation programmatically */
confirmPayment: () => Promise<void>;
/** True while a payment confirmation is in-flight */
isProcessing: boolean;
/** Last payment error, or null */
error: Error | null;
}How it works
The <Checkout> component wraps Stripe Elements and exposes a context. useCheckout reads that context to let you build custom payment UIs or trigger confirmation from outside the default button.
Example
import { Checkout, useCheckout } from '@nozle-js/react';
function PayButton() {
const { confirmPayment, isProcessing, error } = useCheckout();
return (
<div>
<button onClick={confirmPayment} disabled={isProcessing}>
{isProcessing ? 'Processing...' : 'Confirm Payment'}
</button>
{error && <p style={{ color: 'red' }}>{error.message}</p>}
</div>
);
}
function CheckoutPage({ clientSecret }: { clientSecret: string }) {
return (
<Checkout
clientSecret={clientSecret}
publishableKey="pk_live_..."
onSuccess={(id) => console.log('Paid!', id)}
>
<PayButton />
</Checkout>
);
}useSubscribe
Create a subscription server-side. Calls POST /v1/subscribe with the plan code and customer ID from the store.
This hook posts to a server endpoint. If your API key is a publishable key (pk_), the server must handle authorization. For full server-side subscription creation, use a secret key (sk_) in a backend route instead.
Signature
function useSubscribe(): UseSubscribeResultReturn type
interface UseSubscribeResult {
subscribe: (planCode: string) => Promise<any>;
isLoading: boolean;
error: Error | null;
}Example
import { useSubscribe } from '@nozle-js/react';
function SubscribeButton({ planCode }: { planCode: string }) {
const { subscribe, isLoading, error } = useSubscribe();
async function handleClick() {
try {
await subscribe(planCode);
alert('Subscribed successfully!');
} catch {
// error state is set automatically
}
}
return (
<div>
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Subscribing...' : `Subscribe to ${planCode}`}
</button>
{error && <p style={{ color: 'red' }}>{error.message}</p>}
</div>
);
}useNozleClient
Access the raw NozleClient instance for making custom API calls.
Signature
function useNozleClient(): NozleClientReturn type
interface NozleClient {
apiKey: string;
baseUrl: string;
fetch(path: string, init?: RequestInit): Promise<Response>;
can(customerId: string, feature: string): Promise<CanResult>;
}The fetch method automatically injects the Authorization header and base URL, so you only need to pass the path.
Throws an error if called outside of <BillingProvider>.
Example
import { useNozleClient } from '@nozle-js/react';
function InvoiceDownloader({ invoiceId }: { invoiceId: string }) {
const client = useNozleClient();
async function download() {
const res = await client.fetch(`/api/v1/invoices/${invoiceId}/pdf`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
window.open(url);
}
return <button onClick={download}>Download Invoice</button>;
}useBillingContext
Access the full billing context, including the client, workspace ID, and WebSocket connection details.
Signature
function useBillingContext(): BillingContextValueReturn type
interface BillingContextValue {
client: NozleClient | null;
workspaceId: string;
centrifugoUrl: string;
centrifugoToken: string | null;
}Example
import { useBillingContext } from '@nozle-js/react';
function ConnectionStatus() {
const { centrifugoToken, workspaceId } = useBillingContext();
return (
<div>
<p>Workspace: {workspaceId || 'Not set'}</p>
<p>
Real-time:{' '}
{centrifugoToken ? 'Connected' : 'Not connected'}
</p>
</div>
);
}