Created
June 22, 2023 16:47
-
-
Save GLips/1a28cabc098b0c477528bc803defa0d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect } from "react"; | |
import { AnalyticsBrowser, type Analytics } from "@june-so/analytics-next"; | |
import { useInterval } from "@mantine/hooks"; | |
const commands = ["page", "track", "identify"] as const; | |
type JuneCommands = (typeof commands)[number]; | |
let hasSentPageview = false; | |
let analytics: Analytics | undefined; | |
// Make queue static, so it never changes—this way, we can use the return value of | |
// useJune() as a dependency in other hooks without tracking events and pageviews | |
// multiple times. | |
const queue = commands.reduce((q, c) => { | |
return { | |
...q, | |
[c]: june(c), | |
}; | |
}, {} as AnalyticsQueue<JuneCommands>); | |
/** | |
* useJune is a React hook that initializes the June SDK and handles event queuing. | |
* | |
* @param active Whether to initialize the June SDK, used for conditional initialization. | |
* @returns An immutable object that queues June commands for execution once the June API is initialized. | |
*/ | |
export function useJune(active = true) { | |
// Load an Analytics instance. | |
useEffect(() => { | |
const loadAnalytics = async () => { | |
const [response] = await AnalyticsBrowser.load({ | |
writeKey: env.NEXT_PUBLIC_JUNE_WRITE_KEY, | |
}); | |
analytics = response; | |
}; | |
if (active && !analytics) { | |
void loadAnalytics(); | |
} | |
}, [active]); | |
const flushInterval = useInterval(flushQueue, 1000); | |
useEffect(() => { | |
// Send a pageview event once Analytics is ready. | |
if (!hasSentPageview) { | |
void queue.page(); | |
hasSentPageview = true; | |
} | |
// Starte queue flushing. | |
flushInterval.start(); | |
return flushInterval.stop; | |
}, [flushInterval]); | |
return queue; | |
} | |
type Command<C extends FunctionKeys<Analytics>> = { | |
name: C; | |
args: Parameters<Analytics[C]>; | |
}; | |
const commandQueue: Command<FunctionKeys<Analytics>>[] = []; | |
function june<C extends FunctionKeys<Analytics>>( | |
name: C, | |
): (...args: Parameters<Analytics[C]>) => undefined { | |
return (...args) => { | |
queueCommand(name, args); | |
}; | |
} | |
function queueCommand<C extends FunctionKeys<Analytics>>( | |
name: C, | |
args: Parameters<Analytics[C]>, | |
) { | |
commandQueue.push({ name, args }); | |
} | |
function flushQueue() { | |
if (!analytics) return; | |
commandQueue.forEach(({ name, args }) => { | |
if (!analytics) return; // Unnecessary check, but TS doesn't know that. | |
void analytics[name](...args); | |
}); | |
commandQueue.length = 0; | |
} | |
type AnalyticsQueue<TKey extends keyof Analytics> = { | |
[P in TKey]: Analytics[P] extends (...args: any[]) => unknown | |
? (...args: Parameters<Analytics[P]>) => undefined | |
: never; | |
}; | |
type FunctionKeys<T> = { | |
[K in keyof T]: T[K] extends (...args: any[]) => unknown ? K : never; | |
}[keyof T]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment