Where cookie consent belongs in a React app
React mounts after the JavaScript bundle has been downloaded, parsed and executed. Anything in index.html runs before that, including the analytics snippet your marketing team pasted in. That is why consent cannot live only in a React component: by the time the component renders, the tracker may have run.
The reliable layout is two layers. cookieowl loads first in index.html, where it can hold trackers and set Consent Mode defaults before anything else runs. Your components then read the visitor's choice through a tiny hook and render embeds or widgets only when they are allowed.
Install and start it early
With Vite or Create React App, put the script tag in index.html. It is the only setup that blocks trackers written in the HTML:
<head>
<script src="https://cdn.jsdelivr.net/npm/cookieowl@0.1.0/dist/cookieowl.js"
data-policy-url="/privacy" data-consent-mode data-auto-block></script>
<script type="text/plain" data-cookieowl="analytics" async
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
</head>If you prefer the npm package, install it and call init at the top of your entry file, before rendering. Either way there is one instance: when the script tag already created window.cookieowl, the package exports that same object, so the hooks see the choice it manages.
npm install cookieowlimport { createRoot } from 'react-dom/client';
import { cookieowl } from 'cookieowl';
import App from './App';
cookieowl.init({
policyUrl: '/privacy',
consentMode: true,
autoBlock: true,
});
createRoot(document.getElementById('root')!).render(<App />);Render embeds after consent with ConsentGate
ConsentGate renders its children only when every listed category is accepted, and a fallback otherwise. It re-renders on its own when the visitor changes their mind, so a video disappears the moment marketing is withdrawn:
import { ConsentGate } from 'cookieowl/react';
import { cookieowl } from 'cookieowl';
export function VideoSection() {
return (
<ConsentGate
category="marketing"
fallback={
<div className="video-placeholder">
<p>This video is hosted on YouTube, which sets cookies.</p>
<button onClick={() => cookieowl.showPreferences()}>Cookie settings</button>
</div>
}
>
<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID" title="Product tour" />
</ConsentGate>
);
}Read choices with useConsent
useConsent('analytics') returns a boolean that follows the visitor's choice. Called without a category it returns the map of every category. Use it to start a client side SDK only when allowed:
import { useEffect } from 'react';
import { useConsent } from 'cookieowl/react';
export function useAnalytics() {
const allowed = useConsent('analytics');
useEffect(() => {
if (!allowed) return;
const s = document.createElement('script');
s.src = 'https://plausible.io/js/script.js';
s.dataset.domain = 'example.com';
document.head.append(s);
}, [allowed]);
}For a footer link, any element with data-cookieowl-open works, including JSX: <a href="#" data-cookieowl-open>Cookie settings</a>. In TypeScript, data attributes need no extra typing.
Four mistakes to avoid
Starting consent inside a component. A useEffect in your root component runs after the first render, which is after every script in index.html. Initialize cookieowl before createRoot, or better, with the script tag in the head.
Keeping consent in React state only. State resets on every full page load and is invisible to scripts outside React. cookieowl stores the choice in a first party cookie and exposes it to everything on the page, React or not.
Loading an analytics SDK from a bundle import. import statements run when the module loads, before any consent check. Load third party SDKs dynamically, inside the effect that checks useConsent, or keep them as inert scripts in the HTML.
Hiding embeds with CSS. An iframe hidden with display: none still loads and still sets cookies. Do not render it at all until consent, which is exactly what ConsentGate does.
Single page navigation and testing
Consent survives client side navigation because it is stored in a first party cookie, not in React state. Elements with data-cookieowl that a route renders later are released automatically when their category is already accepted, because cookieowl watches the document.
In tests, start a separate instance with storage: 'memory' so every test begins without a choice, and pass it to the helpers through their owl argument:
import { createCookieOwl } from 'cookieowl';
import { ConsentGate } from 'cookieowl/react';
const owl = createCookieOwl({ storage: 'memory', autoShow: false });
render(<ConsentGate category="marketing" owl={owl} fallback="blocked">video</ConsentGate>);
owl.save({ marketing: true });Questions
Is there a React component for the banner?
The banner is plain DOM rendered by cookieowl outside your React tree, so it works before React loads and on pages without React. From components you use the useConsent hook and the ConsentGate component.
Does it work with React 18 and React 19?
Yes. The helpers use only useState and useEffect, so they work from React 16.8 up, with React DOM, Vite, Create React App, Remix or Next.js.
Can I load Google Analytics from a React effect after consent?
Yes. Read useConsent('analytics') and inject the script in an effect when it turns true. Marking the script in index.html with text/plain is simpler and keeps it out of your bundle.
Does cookieowl work with server side rendering?
Importing it on the server is safe: without a DOM, init stores the configuration, accepted() returns false for optional categories and nothing touches document.