refactor(webapp): replace render-time ref initialization (#4729)

## Summary

Replaces render-time ref initialization with lazy state for frozen form
defaults, the tooltip's virtual positioning element, and the side menu's
first-paint visuals. Editable alert fields now update immutable state
snapshots.
This commit is contained in:
Chris Arderne
2026-08-20 09:59:44 +01:00
committed by GitHub
parent 7ab437c8ad
commit a89ce5a709
4 changed files with 39 additions and 47 deletions
@@ -103,13 +103,15 @@ export function ConfigureErrorAlerts({
}
}, [fetcher.state, fetcher.data, closeHref, navigate, toast]);
const emailFieldValues = useRef<string[]>(
const [emailDefaultValues] = useState<string[]>(() =>
existingEmails.length > 0 ? [...existingEmails.map((e) => e.email), ""] : [""]
);
const emailFieldValues = useRef([...emailDefaultValues]);
const webhookFieldValues = useRef<string[]>(
const [webhookDefaultValues] = useState<string[]>(() =>
existingWebhooks.length > 0 ? [...existingWebhooks.map((w) => w.url), ""] : [""]
);
const webhookFieldValues = useRef([...webhookDefaultValues]);
const [form, fields] = useForm<z.infer<typeof ErrorAlertsFormSchema>>({
id: "configure-error-alerts",
@@ -118,8 +120,8 @@ export function ConfigureErrorAlerts({
},
shouldRevalidate: "onSubmit",
defaultValue: {
emails: emailFieldValues.current,
webhooks: webhookFieldValues.current,
emails: emailDefaultValues,
webhooks: webhookDefaultValues,
},
});
const { emails, webhooks, slackChannel, slackIntegrationId } = fields;
@@ -170,7 +172,7 @@ export function ConfigureErrorAlerts({
emailFieldValues.current[index] = e.target.value;
if (
emailFields.length === emailFieldValues.current.length &&
emailFieldValues.current.every((v) => v !== "")
emailFieldValues.current.every((value) => value !== "")
) {
form.insert({ name: emails.name });
}
@@ -324,7 +326,7 @@ export function ConfigureErrorAlerts({
webhookFieldValues.current[index] = e.target.value;
if (
webhookFields.length === webhookFieldValues.current.length &&
webhookFieldValues.current.every((v) => v !== "")
webhookFieldValues.current.every((value) => value !== "")
) {
form.insert({ name: webhooks.name });
}
@@ -366,25 +366,32 @@ export function SideMenu({
const rafRef = useRef<number | null>(null);
// Mirror of `isCollapsed` for the drag handlers (outside React's render cycle; no stale closures).
const isCollapsedRef = useRef(isCollapsed);
// Freeze first-paint values so React never fights the imperative width writes after hydration.
const [initialVisual] = useState(() => {
const collapsed = user.dashboardPreferences.sideMenu?.isCollapsed ?? false;
const expandedWidth = clamp(
user.dashboardPreferences.sideMenu?.width ?? DEFAULT_WIDTH,
DEFAULT_WIDTH,
MAX_WIDTH
);
const width = collapsed ? COLLAPSED_WIDTH : expandedWidth;
const progress = collapsed ? 1 : 0;
return {
expandedWidth,
width,
progress,
style: {
width,
"--sm-collapse": String(progress),
"--sm-label-opacity": String(progressToLabelOpacity(progress)),
} as CSSProperties,
};
});
// The last-committed expanded width; animation targets and re-expansion read from here.
const expandedWidthRef = useRef(
clamp(user.dashboardPreferences.sideMenu?.width ?? DEFAULT_WIDTH, DEFAULT_WIDTH, MAX_WIDTH)
);
// Frozen first-paint width; never changes, so React never fights the imperative width writes.
const initialWidthRef = useRef(
(user.dashboardPreferences.sideMenu?.isCollapsed ?? false)
? COLLAPSED_WIDTH
: expandedWidthRef.current
);
const widthRef = useRef(initialWidthRef.current);
const progressRef = useRef((user.dashboardPreferences.sideMenu?.isCollapsed ?? false) ? 1 : 0);
// Frozen initial style (incl. CSS vars) so the SSR HTML has the right collapsed/expanded visuals
// (no pre-hydration flash). Stable identity, so React never rewrites it after writeVisual.
const initialStyleRef = useRef<CSSProperties>({
width: initialWidthRef.current,
"--sm-collapse": String(progressRef.current),
"--sm-label-opacity": String(progressToLabelOpacity(progressRef.current)),
} as CSSProperties);
const expandedWidthRef = useRef(initialVisual.expandedWidth);
const widthRef = useRef(initialVisual.width);
const progressRef = useRef(initialVisual.progress);
// Removes an in-flight drag's window listeners (set on pointerdown; cleared on finish/unmount).
const dragCleanupRef = useRef<(() => void) | null>(null);
@@ -1066,7 +1073,7 @@ export function SideMenu({
return (
<div
ref={rootRef}
style={initialStyleRef.current}
style={initialVisual.style}
className={cn(
"relative h-full border-r bg-background-bright",
// The accent is the loudest "you are not this user" tell, so "view as user" drops it too —
@@ -4,7 +4,6 @@ import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { usePopper } from "react-popper";
import { useEvent } from "react-use";
import useLazyRef from "~/hooks/useLazyRef";
// Recharts 3.x will have portal support, but until then we're using this:
//https://github.com/recharts/recharts/issues/2458#issuecomment-1063463873
@@ -33,13 +32,9 @@ export interface PopperPortalProps {
export default function TooltipPortal({ active = true, children }: PopperPortalProps) {
const [portalElement, setPortalElement] = useState<HTMLDivElement>();
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>();
const virtualElementRef = useLazyRef(() => new VirtualElement());
const [virtualElement] = useState(() => new VirtualElement());
const { styles, attributes, update } = usePopper(
virtualElementRef.current,
popperElement,
POPPER_OPTIONS
);
const { styles, attributes, update } = usePopper(virtualElement, popperElement, POPPER_OPTIONS);
useEffect(() => {
const el = document.createElement("div");
@@ -50,7 +45,7 @@ export default function TooltipPortal({ active = true, children }: PopperPortalP
}, []);
useEvent("mousemove", ({ clientX: x, clientY: y }) => {
virtualElementRef.current?.update(x, y);
virtualElement.update(x, y);
if (!active) return;
update?.();
});
@@ -59,9 +54,9 @@ export default function TooltipPortal({ active = true, children }: PopperPortalP
if (!active) return;
// Seed from the last known pointer so the tooltip appears at the cursor immediately, even if the
// mouse is held still after hovering onto a point (otherwise it flashes in the top-left corner).
virtualElementRef.current?.update(lastPointer.x, lastPointer.y);
virtualElement.update(lastPointer.x, lastPointer.y);
update?.();
}, [active, update, virtualElementRef]);
}, [active, update, virtualElement]);
if (!portalElement) return null;
-12
View File
@@ -1,12 +0,0 @@
import type { MutableRefObject } from "react";
import { useRef } from "react";
const useLazyRef = <T>(initialValFunc: () => T) => {
const ref: MutableRefObject<T | null> = useRef(null);
if (ref.current === null) {
ref.current = initialValFunc();
}
return ref;
};
export default useLazyRef;