Skip to content

Instantly share code, notes, and snippets.

@GLips
Created June 22, 2023 16:47
Show Gist options
  • Save GLips/1a28cabc098b0c477528bc803defa0d9 to your computer and use it in GitHub Desktop.
Save GLips/1a28cabc098b0c477528bc803defa0d9 to your computer and use it in GitHub Desktop.
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