Skip to main content

useAutoRenewal

Overview

useAutoRenewal is a React hook for managing subscription auto-renewal status across multiple payment providers (Stripe, LemonSqueezy, Polar). It provides automatic provider detection, optimistic updates with rollback on error, toast notifications, and related query invalidation for data consistency.

Source: template/hooks/use-auto-renewal.ts

Signature

function useAutoRenewal(options: UseAutoRenewalOptions): UseAutoRenewalReturn

Parameters

ParameterTypeRequiredDescription
optionsUseAutoRenewalOptionsYesConfiguration object for the hook.

UseAutoRenewalOptions

PropertyTypeDefaultDescription
subscriptionIdstring--Required. The subscription ID to manage auto-renewal for.
enabledbooleantrueWhether to enable the query. Set to false to disable automatic fetching.
onSuccess(data: AutoRenewalStatus) => void--Callback fired when auto-renewal status is successfully loaded. Only fires on transitions to success.
onError(error: AutoRenewalError) => void--Callback fired when loading auto-renewal status fails. Only fires on transitions to error.
onUpdateSuccess(data: UpdateAutoRenewalResponse) => void--Callback fired when auto-renewal is successfully updated.
onUpdateError(error: AutoRenewalError) => void--Callback fired when updating auto-renewal fails.

Return Values

Data

PropertyTypeDescription
autoRenewalStatusAutoRenewalStatus | undefinedThe full auto-renewal status object from the API.
autoRenewalboolean | undefinedWhether auto-renewal is currently enabled.
cancelAtPeriodEndbooleanWhether the subscription will cancel at the end of the current period. Defaults to false.
endDatestring | nullThe subscription end date, or null if not set.
paymentProviderPaymentProviderThe active payment provider (detected automatically or from user selection).

Query Status

PropertyTypeDescription
isLoadingbooleantrue while the initial status fetch is in progress.
isErrorbooleantrue if the status fetch failed.
isSuccessbooleantrue if the status fetch completed successfully.
errorAutoRenewalError | nullThe error from the status fetch, if any.

Mutation Status

PropertyTypeDescription
isUpdatingbooleantrue while an auto-renewal update is in progress.
updateErrorAutoRenewalError | nullThe error from the most recent update attempt.
isUpdateSuccessbooleantrue if the most recent update succeeded.

Actions

PropertyTypeDescription
refetch() => voidManually refetch the auto-renewal status.
updateAutoRenewal(enabled: boolean) => voidUpdate auto-renewal status. Fire-and-forget.
updateAutoRenewalAsync(enabled: boolean) => Promise<UpdateAutoRenewalResponse>Async version that returns a Promise.
enableAutoRenewal() => voidConvenience method to enable auto-renewal.
disableAutoRenewal() => voidConvenience method to disable auto-renewal.
toggleAutoRenewal() => voidToggle the current auto-renewal state. Only works when autoRenewal is not undefined.

Cache Management

PropertyTypeDescription
invalidateCache() => voidMark the auto-renewal cache as stale and trigger a refetch.
clearCache() => voidCompletely remove the auto-renewal cache.

Type Definitions

AutoRenewalStatus

interface AutoRenewalStatus {
subscriptionId: string;
autoRenewal: boolean;
cancelAtPeriodEnd: boolean;
endDate: string | null;
paymentProvider?: string;
}

UpdateAutoRenewalRequest

interface UpdateAutoRenewalRequest {
subscriptionId: string;
enabled: boolean;
paymentProvider: PaymentProvider;
}

UpdateAutoRenewalResponse

interface UpdateAutoRenewalResponse {
success: boolean;
subscription: {
id: string;
autoRenewal: boolean;
cancelAtPeriodEnd: boolean;
endDate: string | null;
[key: string]: any;
};
message: string;
}

Implementation Details

  • Query Key: ['auto-renewal', subscriptionId, paymentProvider] (generated by AUTO_RENEWAL_QUERY_KEY factory function)
  • Stale Time: 2 minutes
  • Garbage Collection Time: 10 minutes
  • Retry Logic: Up to 3 retries. Auth errors (401, 403) and not-found (404) errors are not retried.
  • Optimistic Updates: When updating, the cache is immediately set to the new value. If the mutation fails, the cache rolls back to the previous snapshot.
  • Related Query Invalidation: On successful update, the hook invalidates ['subscriptions'], ['user-subscription'], and ['billing'] query keys.
  • Provider Detection: The hook uses useSelectedCheckoutProvider and usePaymentProvider to automatically determine the active payment provider. User selection takes precedence over config defaults.
  • Callback Transition Detection: The onSuccess and onError callbacks only fire on state transitions (not on every render), using refs to track previous states.
  • API Endpoints:
    • GET /api/payment/{subscriptionId}?provider={provider} -- Fetch auto-renewal status
    • PATCH /api/payment/{subscriptionId} -- Update auto-renewal status

Usage Examples

Auto-Renewal Toggle Switch

import { useAutoRenewal } from '@/hooks/use-auto-renewal';

function AutoRenewalToggle({ subscriptionId }: { subscriptionId: string }) {
const {
autoRenewal,
isLoading,
isUpdating,
toggleAutoRenewal
} = useAutoRenewal({ subscriptionId });

return (
<Switch
checked={autoRenewal ?? false}
disabled={isLoading || isUpdating}
onCheckedChange={toggleAutoRenewal}
label="Auto-renewal"
/>
);
}

With Callbacks

function SubscriptionSettings({ subscriptionId }: { subscriptionId: string }) {
const {
autoRenewal,
cancelAtPeriodEnd,
endDate,
enableAutoRenewal,
disableAutoRenewal,
isUpdating
} = useAutoRenewal({
subscriptionId,
onSuccess: (data) => {
console.log('Status loaded:', data);
},
onUpdateSuccess: (response) => {
console.log('Updated:', response.message);
},
onUpdateError: (error) => {
console.error('Update failed:', error.message);
}
});

return (
<div>
<p>Auto-renewal: {autoRenewal ? 'Enabled' : 'Disabled'}</p>
{cancelAtPeriodEnd && <p>Cancels at end of period: {endDate}</p>}
<button onClick={enableAutoRenewal} disabled={isUpdating || autoRenewal}>
Enable
</button>
<button onClick={disableAutoRenewal} disabled={isUpdating || !autoRenewal}>
Disable
</button>
</div>
);
}