React Integration
Wrap your app (or the portion that needs consent) with ConsentProvider:
import { ConsentProvider } from "@uabcodus/consent/react";
function App() { return ( <ConsentProvider options={{ categories: { necessary: { readOnly: true }, analytics: {}, marketing: {} } }} > <YourApp /> </ConsentProvider> );}ConsentProvider creates a single ConsentInstance via useRef — the instance is stable across re-renders and never changes.
Place it high in your component tree, typically wrapping your entire app. You can have multiple providers if you need separate consent scopes (uncommon).
The useConsent hook
Section titled “The useConsent hook”const consent = useConsent();Returns the ConsentInstance. It also auto-subscribes to state changes — any component using useConsent re-renders whenever consent state updates.
import { useConsent } from "@uabcodus/consent/react";
function ConsentBanner() { const consent = useConsent();
if (consent.state.valid) return null;
return ( <div> <button onClick={() => consent.accept("all")}>Accept All</button> <button onClick={() => consent.accept("necessary")}>Necessary Only</button> </div> );}
function AnalyticsTracker() { const consent = useConsent();
if (consent.acceptedCategory("analytics")) { return <Script src="https://www.googletagmanager.com/gtag/js" />; }
return null;}Throws if called outside a ConsentProvider.
TypeScript
Section titled “TypeScript”Pass your category config type to ConsentProvider for full type inference:
const config = { categories: { necessary: { readOnly: true }, analytics: {}, marketing: {} } as const};
function App() { return ( <ConsentProvider options={config}> <ConsentBanner /> </ConsentProvider> );}
function ConsentBanner() { const consent = useConsent();
// Fully typed: "necessary" | "analytics" | "marketing" | "all" consent.accept("analytics");
return null;}Common patterns
Section titled “Common patterns”Conditional rendering
Section titled “Conditional rendering”function BannerOrTracker() { const consent = useConsent();
if (!consent.state.valid) { return <ConsentBanner />; }
return <ConsentStatus />;}Preferences panel
Section titled “Preferences panel”function PreferencesPanel() { const consent = useConsent(); const [open, setOpen] = useState(false);
return ( <> <button onClick={() => setOpen(true)}>Preferences</button> <Dialog open={open} onClose={() => setOpen(false)}> {Object.entries(consent.state.categories).map(([name, cat]) => cat.readOnly ? null : ( <label key={name}> <input type="checkbox" checked={cat.accepted} onChange={(e) => (e.target.checked ? consent.accept(name) : consent.reject(name))} /> {name} </label> ) )} <button onClick={() => { consent.accept("all"); setOpen(false); }} > Accept All </button> </Dialog> </> );}Event-based vs state-based
Section titled “Event-based vs state-based”The hook triggers re-renders on every state change. If you need to run side effects without re-rendering, use the event system instead:
function GtagSyncer() { const consent = useConsent();
useEffect(() => { const unsub = consent.on("change", ({ cookie }) => { window.gtag?.("consent", "update", { analytics_storage: cookie.categories.includes("analytics") ? "granted" : "denied" }); }); return unsub; }, [consent]);
return null;}SSR note
Section titled “SSR note”The library is client-side. If you’re server-rendering, make sure ConsentProvider only renders
on the client (wrap it in a component with "use client" or use client:only in Astro).
On the initial render, consent.state.valid will be false until the cookie is read. This means
your banner may flash on every page load if you’re not careful. To avoid this, read the cookie
server-side and pass it as initialCookie:
// In your SSR setup:const cookieValue = parseConsentCookie(cookies().get("cc_cookie"));
<ConsentProvider options={{ initialCookie: cookieValue, categories: { /* ... */ } }}> <App /></ConsentProvider>;This hydrates the consent state immediately — no flash, no flicker.