Skip to main content

Documentation

Documentation

Replace useConsentManager()

useConsentManager() returned the whole consent state, so every component that called it re-rendered on every change. v3 has one hook per field. Run the codemod first:

npx @c15t/cli codemods use-consent-manager-to-hooks --dry-run --json

Review the proposed files, then run it again without --dry-run. For this v2 component:

Before (v2): components/accept-button.tsx
import { useConsentManager } from '@c15t/react';

export function AcceptButton() {
	const { activeUI, has, saveConsents } = useConsentManager();
	if (activeUI !== 'banner' || has('marketing')) return null;
	return (
		<button type="button" onClick={() => void saveConsents('all')}>
			Accept
		</button>
	);
}

the codemod writes:

After (v3): components/accept-button.tsx
import { useActiveUI, useConsent } from '@c15t/react';
import { useHeadlessConsentUI } from '@c15t/react/headless';

export function AcceptButton() {
	const activeUI = useActiveUI() ?? 'none';
	const hasMarketing = useConsent('marketing');
	const { saveCustomPreferences: saveConsents } = useHeadlessConsentUI();
	if (activeUI !== 'banner' || hasMarketing) return null;
	return (
		<button type="button" onClick={() => void saveConsents('all')}>
			Accept
		</button>
	);
}

The codemod keeps the entry you imported from. Change @c15t/react to c15t/react afterwards if you moved to the umbrella package. Fields it cannot rewrite stay on a useConsentManager() call under a TODO(c15t v3) comment, which fails the build until you replace them. The table lists the v2 fields components read. The other fields held provider configuration, such as legalLinks, scripts and storageConfig, or store internals; set configuration through ConsentProvider props. Import hooks from c15t/react, c15t/next or c15t/tanstack-start, and useHeadlessConsentUI from the matching /headless entry.

v2 fieldv3 replacement
activeUIuseActiveUI(). It returns null until c15t picks a surface, where v2 returned 'none'.
setActiveUI(ui)useSetActiveUI()
has(category)useConsent(category), one call per category at the top of the component
consentsuseConsents()
saveConsents('all')useHeadlessConsentUI().saveCustomPreferences('all')
saveConsents('necessary')useHeadlessConsentUI().saveCustomPreferences('none')
saveConsents('custom')useConsentDraft().save(), or useHeadlessConsentUI().saveCustomPreferences() inside a ConsentDraftProvider
selectedConsentsuseConsentDraft().values
setSelectedConsent(name, value)useConsentDraft().set(name, value)
setConsent(name, value)useSaveConsents(), called with { [name]: value }
consentInfouseExplicitChoice()
hasConsented()useExplicitChoice() !== null
consentCategories, consentTypes, getDisplayedConsents()useConsentDraft().displayedCategories, with labels from useTranslations().consentTypes
policyCategoriesusePolicyCategories(). The v2 list started with 'necessary'; this one does not.
policyScopeModeusePolicyScopeMode()
policyBannerusePromptPresentation()
policyDialogusePreferencesPresentation()
modeluseModel(). It returns null while no policy matches, where v2 returned 'opt-in'.
brandinguseBranding(). It returns null when unset, where v2 returned 'c15t'.
iabuseIABSnapshot()
locationInfouseLocation()
overrides, setOverrides()useOverrides(), useSetOverrides()
setLanguage()useSetLanguage()
user, identifyUser()useUser(), useIdentify()
translationConfiguseTranslations() for the active strings
subscribeToConsentChanges(listener)useSubscribeToConsentChanges(), or the onPermissionsChanged callback
updateConsentCategories(categories)useRegisterConsentCategories()
managerNothing. Use the hooks above.

saveCustomPreferences() saves the way the stock buttons do. It closes the banner or dialog after a successful save. useSaveConsents() calls the engine directly and does not close anything.

In v2, useConsentManager() kept one draft per component. In v3, useConsentDraft() and useHeadlessConsentUI() share a draft only inside a ConsentDraftProvider. Without one, saveCustomPreferences() saves a fresh draft and ignores what useConsentDraft().set() staged, even in the same component. Save with useConsentDraft().save(), or render the code that stages and the code that saves inside one ConsentDraftProvider. ConsentWidget already includes one. The codemod adds a TODO(c15t v3) comment where this applies.