mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(monitoring): defer web-vitals sample draw to useEffect to satisfy react-hooks/purity
The 10% sampling decision in WebVitalsReporter (v1.4.33 F19) was
computed inline in useMemo(), which the react-hooks/purity lint rule
rejects: Math.random() is impure and the render body must be
idempotent. The original useMemo() happened to be stable in practice
(empty dep array) but the rule fires regardless because the
expression inside the memo factory is impure.
Move the draw into useEffect(() => {}, []) and stash the result in a
useRef so every reportWebVitals callback in the same navigation reads
the same decision. SSR seeds the ref to null, so a pre-hydration
beacon (which would have no rate-limit relief value) is dropped; the
first post-hydration callback finds the ref populated and decides
whether to POST. Bundle and runtime cost unchanged — same one draw
per mount, same all-or-none per page-load. ESLint clean.
This commit is contained in:
1 parent
99af530490
commit
1a101505c4
1 file changed
+17
-9
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useReportWebVitals } from "next/web-vitals";
|
||||
|
||||
/**
|
||||
@@ -38,16 +38,24 @@ const SAMPLE_RATE = 0.1;
|
||||
|
||||
export function WebVitalsReporter() {
|
||||
// Decide once per mount whether this page-load reports its vitals.
|
||||
// `useMemo` with an empty dep array keeps the decision stable across
|
||||
// every metric callback inside the same navigation — partial sampling
|
||||
// would mix LCP-without-CLS noise into the wide-event aggregates.
|
||||
// `SSR` evaluates Math.random() to a stable false branch (Node), but
|
||||
// the hook only fires post-hydration so the decision uses the
|
||||
// browser's PRNG.
|
||||
const sampled = useMemo(() => Math.random() < SAMPLE_RATE, []);
|
||||
// Math.random() is impure, so we deferred the draw to `useEffect`
|
||||
// (React's purity contract forbids running impure work inside
|
||||
// useMemo / the render body). The ref keeps the decision stable
|
||||
// across every metric callback inside the same navigation —
|
||||
// partial sampling would mix LCP-without-CLS noise into the wide-
|
||||
// event aggregates. SSR initialises the ref to `null` (no sample
|
||||
// decision yet); the first reportWebVitals callback fires post-
|
||||
// hydration once the effect has filled the slot.
|
||||
const sampledRef = useRef<boolean | null>(null);
|
||||
useEffect(() => {
|
||||
sampledRef.current = Math.random() < SAMPLE_RATE;
|
||||
}, []);
|
||||
|
||||
useReportWebVitals((metric) => {
|
||||
if (!sampled) return;
|
||||
// If the effect hasn't yet seeded the sample decision (very first
|
||||
// pre-hydration measurement), drop the beacon — over-sampling at
|
||||
// first paint would defeat the rate-limit relief.
|
||||
if (sampledRef.current !== true) return;
|
||||
|
||||
const body = JSON.stringify({
|
||||
id: metric.id,
|
||||
|
||||
Reference in new issue
Block a user