React Snippets
This documentation site uses React and Next.js. Here are some snippets and recipes for how use airgap.js
in this codebase:
lib/hooks/useConsentManager.tsx
:
import type {AirgapAPI,TranscendAPI,PreInitTranscendAPI,} from '@transcend-io/airgap.js-types';import { useState, useContext, createContext, useEffect } from 'react';import Script from 'next/script';// Add type for Airgap before initializationtype PreInitAirgapAPI = Required<Pick<AirgapAPI, 'readyQueue' | 'ready'>>;declare global {interface Window {/** airgap.js interface */airgap?: PreInitAirgapAPI | AirgapAPI;/** Transcend Consent Manager interface */transcend?: PreInitTranscendAPI | TranscendAPI;}}interface ConsentAPI {airgap?: AirgapAPI;transcend?: TranscendAPI;}interface ConsentProviderProps {children: React.ReactNode;airgapSrc: string;}export const ConsentContext = createContext<ConsentAPI>({});/*** React context provider for `window.airgap` and `window.transcend`* @see https://docs.transcend.io/docs/consent/faq*/export const ConsentProvider: React.FC<ConsentProviderProps> = ({children,airgapSrc,}) => {const [airgap, setAirgap] = useState<AirgapAPI | undefined>(undefined);const [transcend, setTranscend] = useState<TranscendAPI | undefined>(undefined);// `useEffect` ensures this is only executed in browseruseEffect(() => {// Stub transcend with PreInit APIif (!window.transcend) {const preInitTranscend: PreInitTranscendAPI = {readyQueue: [],ready(callback) {this.readyQueue.push(callback);},...(window.transcend || {}),};window.transcend = preInitTranscend;}if (!window.airgap) {const preInitAirgap: PreInitAirgapAPI = {readyQueue: [],ready(callback) {this.readyQueue.push(callback);},...(window.airgap || {}),};window.airgap = preInitAirgap;}// Wait for airgap.js core to load, and set it in the React statewindow.airgap.ready((airgap) => {setAirgap(airgap);});// Wait for Consent Manager UI to load, and set it in the React statewindow.transcend.ready((transcend) => {setTranscend(transcend);});}, []);return (<><Script src={airgapSrc} /><ConsentContext.Provider value={{ airgap, transcend }}>{children}</ConsentContext.Provider></>);};export const useConsentManager = (): ConsentAPI => useContext(ConsentContext);
And wrap your application in the consent provider.
pages/_app.tsx
:
import { ConsentProvider } from '../lib/hooks/useConsentManager';export default function MyApp({ Component, pageProps }) {return (// Replace `airgapSrc` with your bundle URL<ConsentProvider airgapSrc="https://cdn.transcend.io/cm/f8d8c73f-3189-4ab4-c138-fa9922f76812/airgap.js"><Component {...pageProps} />;</ConsentProvider>);}
Now your components can interact with window.airgap
and the consent manager UI (window.transcend
) by calling const { transcend, airgap } = useConsentManager();
. See the next snippet for an example! Beware: you are now importing airgap.js asynchronously. Since airgap.js can only regulate network traffic after it is loaded, you are now responsible for ensuring no trackers load before airgap.js!
Add California's CCPA/CPRA "Your Privacy Choices" opt-out icon to your website footer (replacing the "Do Not Sell or Share My Personal Information" link).
components/YourPrivacyChoices.tsx
:
import React, { useState, useEffect } from 'react';import { useConsentManager } from '../lib/consent-manager';/*** This import assumes you are using `@svgr/webpack` to load SVGs as React components* @see https://www.npmjs.com/package/@svgr/webpack* @see https://react-svgr.com/*/import PrivacyChoicesIcon from './privacy-choices-icon.svg';export interface YourPrivacyChoicesProps {}export const YourPrivacyChoicesInterior: React.FC<JSX.IntrinsicElements['button']> = ({ onClick }) => {return (<><button onClick={onClick} className="privacy-choices-button"><div className="privacy-choices-button-layout"><PrivacyChoicesIcon className="privacy-choices-icon" /><span>Your Privacy Choices</span></div></button><style jsx>{`.privacy-choices-button-layout {display: flex;align-items: center;}.privacy-choices-button {all: unset;}.privacy-choices-button {height: var(--lineHeight);cursor: pointer;}.privacy-choices-button-layout :global(.privacy-choices-icon) {height: 0.9em;padding-right: 0.4em;}`}</style></>);};/*** CPRA Your Privacy Choices button** @see https://cppa.ca.gov/regulations/pdf/20221102_mod_text.pdf* @see https://oag.ca.gov/privacy/ccpa/icons-download*/export const YourPrivacyChoices: React.FC<YourPrivacyChoicesProps> = ({}) => {const { transcend, airgap } = useConsentManager();const [consentEnabled, setConsentEnabled] = useState<boolean>(false);useEffect(() => {// Show the 'Your Privacy Choices' button based on `airgap.getRegimes`function getConsentEnabled(): boolean {if (!airgap) {return false;}const regimes = airgap.getRegimes();// We display this button if CPRA is present in the detected regimesconst consentEnabled =typeof regimes !== 'undefined' && regimes.has('CPRA');return consentEnabled;}setConsentEnabled(getConsentEnabled());}, [airgap]);if (!consentEnabled) {return null;}return (<YourPrivacyChoicesInterioronClick={() =>transcend?.showConsentManager({ viewState: 'CompleteOptions' })}/>);};
And lastly, here is a download link for a compressed + optimized SVG of the CPRA opt out icon (imported above as './privacy-choices-icon.svg'
)