---
title: Use Consent Manager
description: "`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:"
lastModified: "2026-10-12T12:44:54+01:00"
lastAuthor: Christopher Burns
---
## 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:

```bash
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:

```tsx title="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:

```tsx title="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 field|v3 replacement|
|--|--|
|`activeUI`|`useActiveUI()`. 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|
|`consents`|`useConsents()`|
|`saveConsents('all')`|`useHeadlessConsentUI().saveCustomPreferences('all')`|
|`saveConsents('necessary')`|`useHeadlessConsentUI().saveCustomPreferences('none')`|
|`saveConsents('custom')`|`useConsentDraft().save()`, or `useHeadlessConsentUI().saveCustomPreferences()` inside a `ConsentDraftProvider`|
|`selectedConsents`|`useConsentDraft().values`|
|`setSelectedConsent(name, value)`|`useConsentDraft().set(name, value)`|
|`setConsent(name, value)`|`useSaveConsents()`, called with `{ [name]: value }`|
|`consentInfo`|`useExplicitChoice()`|
|`hasConsented()`|`useExplicitChoice() !== null`|
|`consentCategories`, `consentTypes`, `getDisplayedConsents()`|`useConsentDraft().displayedCategories`, with labels from `useTranslations().consentTypes`|
|`policyCategories`|`usePolicyCategories()`. The v2 list started with `'necessary'`; this one does not.|
|`policyScopeMode`|`usePolicyScopeMode()`|
|`policyBanner`|`usePromptPresentation()`|
|`policyDialog`|`usePreferencesPresentation()`|
|`model`|`useModel()`. It returns `null` while no policy matches, where v2 returned `'opt-in'`.|
|`branding`|`useBranding()`. It returns `null` when unset, where v2 returned `'c15t'`.|
|`iab`|`useIABSnapshot()`|
|`locationInfo`|`useLocation()`|
|`overrides`, `setOverrides()`|`useOverrides()`, `useSetOverrides()`|
|`setLanguage()`|`useSetLanguage()`|
|`user`, `identifyUser()`|`useUser()`, `useIdentify()`|
|`translationConfig`|`useTranslations()` for the active strings|
|`subscribeToConsentChanges(listener)`|`useSubscribeToConsentChanges()`, or the `onPermissionsChanged` callback|
|`updateConsentCategories(categories)`|`useRegisterConsentCategories()`|
|`manager`|Nothing. 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.
