Files
Chris Arderne 9dca03f682 chore: enforce exhaustive React hook dependencies (#4712)
## Summary

Enables exhaustive React Hook dependency checking and resolves the
existing violations across the dashboard and React hooks package.
Effects and callbacks now track current values without introducing
request, subscription, or render loops.

## Design

Dependencies are included directly when the hook lifecycle should follow
them. Timers, Remix fetchers, and realtime subscriptions use stable
callbacks or latest-value refs where restarting work would change
behavior.

Unnecessary memoization was removed where ordinary derivation is
clearer. Full lint and typechecks for the webapp and React hooks package
pass.
2026-08-20 09:59:38 +01:00

93 lines
2.9 KiB
TypeScript

import { useLocation } from "@remix-run/react";
import posthog from "posthog-js";
import { useCallback, useEffect, useRef } from "react";
import { useOrganizationChanged } from "./useOrganizations";
import { useOptionalUser, useUserChanged } from "./useUser";
import { useProjectChanged } from "./useProject";
export const usePostHog = (
apiKey?: string,
uiHost?: string,
logging = false,
debug = false
): void => {
const postHogInitialized = useRef(false);
const location = useLocation();
const user = useOptionalUser();
//start PostHog once
useEffect(() => {
if (apiKey === undefined || apiKey === "") return;
if (postHogInitialized.current === true) return;
if (logging) console.log("Initializing PostHog");
posthog.init(apiKey, {
// Same-origin first-party proxy (see app/routes/ph.$.ts) that forwards to
// PostHog Cloud EU server-side.
api_host: "/ph",
// Point the toolbar at the real PostHog UI; without it, it falls back to /ph.
ui_host: uiHost,
cross_subdomain_cookie: true,
opt_in_site_apps: true,
debug,
loaded: function (posthog) {
if (logging) console.log("PostHog loaded");
if (user !== undefined) {
if (logging) console.log("Loaded: Identifying user", user);
posthog.identify(user.id, { email: user.email });
}
},
});
postHogInitialized.current = true;
}, [apiKey, uiHost, logging, debug, user]);
useUserChanged((user) => {
if (postHogInitialized.current === false) return;
if (logging) console.log("User changed");
if (user) {
if (logging) console.log("Identifying user", user);
posthog.identify(user.id, { email: user.email });
} else {
if (logging) console.log("Resetting user");
posthog.reset();
}
});
useOrganizationChanged((org) => {
if (postHogInitialized.current === false) return;
if (org) {
if (logging) console.log(`Grouping by organization`, org);
posthog.group("organization", org.id);
} else {
//reset the groups when you go to one of the top-level pages
if (logging) console.log("Resetting groups");
posthog.resetGroups();
}
});
useProjectChanged((project) => {
if (postHogInitialized.current === false) return;
if (project) {
if (logging) console.log(`Grouping by project`, project);
posthog.group("project", project.id);
}
});
//page view
useEffect(() => {
if (postHogInitialized.current === false) return;
posthog.capture("$pageview");
}, [location, logging]);
};
export function usePostHogTracking() {
const capture = useCallback((eventName: string, properties?: Record<string, unknown>) => {
posthog.capture(eventName, properties);
}, []);
const startSessionRecording = useCallback(() => {
posthog.startSessionRecording();
}, []);
return { capture, startSessionRecording };
}