Implement cookie consent in your SPA

Introduction

Goal

Implement cookie consent in a Bloomreach SPA SDK-based frontend application.

Background

Several Bloomreach Commerce Experience Cloud features, including the Discovery pixel and Content personalization, use cookies to store information about users. In many jurisdictions, this requires explicit consent from the user.

It is up to the frontend development team to implement cookie consent in their application. This page provides an example implementation for React frontends using the popular Osano Cookie Consent Javascript library.

👍

Tip

A slightly different implementation using the react-cookie-consent library can be found in the Reference SPA.

Implementation

Add the Osano Cookie Consent library

Install the cookieconsent library in your application:

npm install cookieconsent

Add the Osano Cookie Consent CSS and Javascript to public/index.html (just before the closing </head> element):

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.css" />
<script src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js"></script>

Create a Cookie Consent utility library

Create a cookie consent utility library in src/utils/cookieconsent.ts:

/* eslint-disable max-len */

declare global {
  interface Window {
    cookieconsent?: {
      status: {
        allow: 'allow';
        deny: 'deny';
        dismiss: 'dismiss';
      };
      initialise(
        config: {
          [key: string]: unknown;
          onInitialise(status: keyof Required<Window>['cookieconsent']['status']): void;
          onStatusChange(status: keyof Required<Window>['cookieconsent']['status']): void;
        },
        complete: (popup: Required<Window>['cookieconsent']['Popup']) => void,
      ): void;
      utils: {
        getCookie(name: string): string;
      };
      Popup: unknown;
    };
  }
}

const COOKIE_CONSENTS_EXPIRATION_VALUE = 28; // Days

export const isConsentReceived = (): boolean => {
  const { cookieconsent } = window || {};

  return !!cookieconsent && cookieconsent.utils.getCookie('cookieconsent_status') === cookieconsent.status.allow;
};

let isConsentInitialised = false;

const CookieConsentInit = (): void => {
  if (isConsentInitialised) return;

  window.cookieconsent?.initialise?.(
    {
      palette: {
        popup: {
          background: '#000',
        },
        button: {
          background: '#f1d600',
        },
      },
      cookie: {
        expiryDays: COOKIE_CONSENTS_EXPIRATION_VALUE,
      },
      showLink: false,
      type: 'opt-in',
      onInitialise: (status) => {
        if (status === window.cookieconsent?.status.allow) {
          // add cookie consent dependent code that should run on start up
        }
      },
      onStatusChange: (status) => {
        if (status === window.cookieconsent?.status.allow) {
          // add cookie consent dependent code that should run any time the status is changed
        }
      },
    },
    (popup) => {
      isConsentInitialised = !!popup;
    },
  );
};

export default CookieConsentInit;

Notes:

  • Use the Osana Cookie Consent configuration wizard to create your own custom configuration for the cookie consent popup. See the Osano Cookie Consent Javascript API for advanced options.
  • You can add any code that depends on cookie consent and should run on the cookie consent library's initialization to the onInitialise callback hook. From the Osano Cookie Consent Javascript API:
    • "This is called on start up, with the current chosen compliance. It can be used to tell you if the user has already consented or not as soon as you initialise the tool."
  • You can add any code that depends on cookie consent and should run when the cookie consent status changes to the onStatusChange callback hook. From the Osano Cookie Consent Javascript API:
    • "This is called any time the status is changed. This can be used to react to changes that are made to the compliance level. You can use the popup instance functions from within these callbacks too. I.E. ‘this.hasAnswered()’ and ‘this.hasConsented()’."
  • You can import isConsentReceived elsewhere in your code and use it to conditionally render cookie consent dependent components like BrPixel (see below).

Create a CookieConsent component

Create a CookieConsent component in src/components/CookieConsent.tsx:

import { useEffect } from 'react';
import CookieConsentInit, { isConsentReceived } from '../utils/cookieconsent';

interface CookieConsentProps {
  isPreview: boolean;
  path: string;
}

export const CookieConsent = ({ isPreview, path }: CookieConsentProps): null => {
  useEffect(() => {
    if (!isPreview) {
      CookieConsentInit();
    }
  }, [isPreview]);

  useEffect(() => {
    if (!isPreview && isConsentReceived()) {
      // add cookie consent dependent code that should run any time the page is rendered
    }
  }, [path, isPreview]);

  return null;
};

Note:

  • You can add any code that depends on cookie consent and should run each time the page is rendered within the if (!isPreview && isConsentReceived()) { } condition inside CookieConsent.

In src/components/index.ts, add:

export * from './CookieConsent';

Add the Cookie Consent popup to your app

In src/App.tsx, add CookieConsent to the list of components imported from './components':

import {
  /* existing components */
  CookieConsent,
} from './components';

Then add the following somewhere within you <BrPage> element to render the cookie consent popup:

<BrPageContext.Consumer>
            {(page) => <CookieConsent isPreview={!!page?.isPreview()} path={configuration.path ?? '/'} />}
          </BrPageContext.Consumer>

Conditional rendering of components depending on cookie consent

Optionally, also add to src/App.tsx:

import { isConsentReceived } from './utils/cookieconsent';

Then use isConsentReceived() to toggle cookie consent dependent components like BrPixel. For example:

{({ smAccountId, smDomainKey }) =>
                isConsentReceived() && (
                  <BrPixel
                    accountId={smAccountId ?? ''}
                    domainKey={smDomainKey ?? ''}
                    page={page!}
                    pageType="search"
                    pageLabels="pacific,nut,bolt,commerce"
                    type="pageview"
                  />
                )
              }

Summary

The example implementation presented on this page provided four ways to handle code that requires cookie consent:

  • Code depending on cookie consent that should run on the cookie consent library's initialization → place in the cookieconsent utility library inside the onInitialise callback hook.
  • Code depending on cookie consent that should run when the cookie consent status changes → place in the cookieconsent utility library inside the onStatusChange callback hook.
  • Code depending on cookie consent that should run each time a page is rendered → place in the CookieConsent component inside the if (!isPreview && isConsentReceived()) { } condition.
  • Frontend components that should only be rendered if cookie consent is accepted → import isConsentReceived from the cookieconsent utility library and use as condition for rendering.