cookieowl

Chapter VI of IX

Cookie consent in React, without a provider maze

A React app still needs its trackers held before React even mounts. Here is where to start cookieowl, and how to read consent from components.

By Updated 7 min read

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:

index.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.

Terminal
npm install cookieowl
src/main.tsx
import { 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:

src/VideoSection.tsx
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:

src/useAnalytics.ts
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:

VideoSection.test.tsx
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.

Hoot suggests

Nothing here. Hoot looked under every cookie jar.