chore(webapp): scope imperative component refs (#4730)

## Summary

Scopes React Compiler diagnostics to component and hook statements where
refs intentionally coordinate editors, animations, polling, deferred
callbacks, and other imperative integrations. Other compiler diagnostics
remain active in those components.
This commit is contained in:
Chris Arderne
2026-08-20 09:59:45 +01:00
committed by GitHub
parent a89ce5a709
commit 4d040e13be
22 changed files with 39 additions and 0 deletions
@@ -56,6 +56,7 @@ export function FeatureFlagsDialog({
const saveFetcher = useFetcher<ActionData>();
const loadFeatureFlags = loadFetcher.load;
const onOpenChangeRef = useRef(onOpenChange);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
onOpenChangeRef.current = onOpenChange;
const [overrides, setOverrides] = useState<Record<string, unknown>>({});
@@ -94,6 +94,7 @@ export function JSONEditor(opts: JSONEditorProps) {
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
@@ -196,6 +196,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
@@ -48,6 +48,7 @@ export function TextEditor(opts: TextEditorProps) {
const editor = useRef<HTMLDivElement>(null);
const settings: Omit<UseCodeMirror, "onBlur"> = {
...opts,
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
container: editor.current,
extensions,
editable: !readOnly,
@@ -123,6 +123,7 @@ export function DashboardAgentChat({
// The path this chat last rendered on. React never unmounts on a page teardown, so an
// unmount whose live URL has moved is the router having navigated out from under it.
const renderedPathRef = useRef(location.pathname);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
renderedPathRef.current = location.pathname;
const transport = useTriggerChatTransport<typeof dashboardAgent>({
@@ -209,6 +210,7 @@ export function DashboardAgentChat({
});
const orderRef = useRef(createTranscriptOrder(initialMessages));
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
const messages = orderTranscript(rawMessages, orderRef.current);
// Read here, not in the panel, so it re-reads as each turn settles.
@@ -359,6 +361,7 @@ export function DashboardAgentChat({
const navigatedRef = useRef<Set<string> | null>(null);
if (navigatedRef.current === null) {
navigatedRef.current = new Set();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
pendingNavigateIntents(initialMessages, navigatedRef.current);
}
useEffect(() => {
@@ -374,6 +377,7 @@ export function DashboardAgentChat({
const watchProposedRef = useRef<Set<string> | null>(null);
if (watchProposedRef.current === null) {
watchProposedRef.current = new Set();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
pendingWatchIntents(initialMessages, watchProposedRef.current);
}
useEffect(() => {
@@ -388,6 +392,7 @@ export function DashboardAgentChat({
}, [transport, chatId, aiStop]);
const teardownRef = useRef<() => void>(() => {});
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
teardownRef.current = () => {
if (status !== "streaming" && status !== "submitted") return;
const reason = unmountTeardown({
@@ -401,6 +406,7 @@ export function DashboardAgentChat({
// Read by the settle effect, which must not re-run when the transcript changes.
const messagesRef = useRef(messages);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
messagesRef.current = messages;
const prevStatus = useRef(status);
@@ -126,7 +126,9 @@ export function winningInvestigationOccurrences(messages: UIMessage[]): Map<stri
function useInvestigationWinners(messages: UIMessage[]): Map<string, string> {
const previous = useRef<Map<string, string>>();
const next = useMemo(() => winningInvestigationOccurrences(messages), [messages]);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
previous.current = reuseWinners(previous.current, next);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
return previous.current;
}
@@ -174,6 +174,7 @@ export function DashboardAgentPanel({
const loadHistory = useMemo(
() =>
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
createCoalescedReload(async () => {
try {
const res = await fetch(actionPath);
@@ -532,6 +532,7 @@ export function SideMenu({
// object each render, so depending on it would fire the cleanup (flushing the debounce) every
// render — and drags re-render constantly — instead of only on unmount.
const flushPendingPreferencesRef = useRef<() => void>();
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
flushPendingPreferencesRef.current = () => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
@@ -34,6 +34,7 @@ export function useReorderableList<T>({
const [order, setOrder] = useState<string[]>(() => initialOrder ?? items.map(itemKey));
const resetOrderRef = useRef({ initialOrder, items, itemKey });
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
resetOrderRef.current = { initialOrder, items, itemKey };
// Only an organization switch resets user-managed order. Keep the latest inputs in a ref so
@@ -290,7 +290,9 @@ export function AgentDotMatrix({
const playlistKey = playlist.join(",");
const paletteObjRef = useRef(paletteObj);
const playlistRef = useRef(playlist);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
paletteObjRef.current = paletteObj;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
playlistRef.current = playlist;
useEffect(() => {
@@ -81,6 +81,7 @@ export function DateField({
});
const stateValueRef = useRef(state.value);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
stateValueRef.current = state.value;
// Sync only when the passed value or timezone mode changes. Depending on state.value directly
@@ -100,7 +100,9 @@ function collapsibleHandleClassName(show: boolean) {
function useFrozenValue<T>(value: T | null | undefined): T | null | undefined {
const ref = useRef(value);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
if (value != null) ref.current = value;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
return ref.current;
}
@@ -55,6 +55,7 @@ export function useZoomSelection(): UseZoomSelectionReturn {
const stateRef = useRef<ZoomSelectionState>(state);
// Keep ref in sync with state
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
stateRef.current = state;
const startSelection = useCallback((label: string) => {
@@ -505,6 +505,7 @@ export function QueryEditor({
// Use a ref so the effect can read chartConfig without re-firing on every config tweak
const chartConfigRef = useRef(chartConfig);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
chartConfigRef.current = chartConfig;
// Reset chart config only when a column referenced by the current config is no
@@ -562,6 +563,7 @@ export function QueryEditor({
}, []);
// Compute current save data for the save render prop
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
const currentQuery = editorRef.current?.getQuery() ?? "";
const saveData: QueryEditorSaveData = {
title: queryTitle ?? "Untitled Query",
@@ -790,6 +792,7 @@ export function QueryEditor({
onRename={handleRenameTitle}
/>
}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? defaultQuery}
data={{
rows: results.rows,
@@ -844,6 +847,7 @@ export function QueryEditor({
<ResultsChart
rows={results.rows}
columns={results.columns}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? defaultQuery}
chartConfig={chartConfig}
onChartConfigChange={handleChartConfigChange}
@@ -893,6 +897,7 @@ export function QueryEditor({
<ResultsBigNumber
rows={results.rows}
columns={results.columns}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? defaultQuery}
bigNumberConfig={bigNumberConfig}
onBigNumberConfigChange={setBigNumberConfig}
@@ -955,6 +960,7 @@ export function QueryEditor({
{mode.type === "standalone" && (
<SaveToDashboardDialog
title={queryTitle ?? "Untitled Query"}
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
query={editorRef.current?.getQuery() ?? ""}
config={
resultsView === "table"
@@ -100,6 +100,7 @@ function useChildRunStatusesTooltip({
key: `child-statuses-${friendlyId}`,
});
const fetcherStateRef = useRef(fetcher.state);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
fetcherStateRef.current = fetcher.state;
const [childStatuses, setChildStatuses] = useState<ChildStatusEntry[] | undefined>();
@@ -120,6 +121,7 @@ function useChildRunStatusesTooltip({
// Keep the latest loader callback available to the polling interval
// without recreating the interval on every render.
const loadChildStatusesRef = useRef(loadChildStatuses);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
loadChildStatusesRef.current = loadChildStatuses;
const stopPolling = useCallback(() => {
@@ -285,6 +285,7 @@ function useAgentSessionMessages({
// `scheduleFlush`. The Map *reference* changes on every flush so React
// detects the state update and the downstream `useMemo` recomputes.
const [messagesById, setMessagesById] = useState<Map<string, UIMessage>>(
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
() => new Map(pendingRef.current)
);
@@ -294,6 +295,7 @@ function useAgentSessionMessages({
const lastFlushAtRef = useRef<number>(0);
const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const scheduleFlush = useRef<() => void>(() => {});
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
scheduleFlush.current = () => {
if (pendingTimerRef.current !== null) return; // already scheduled
const now = Date.now();
@@ -670,6 +672,7 @@ function useAgentSessionMessages({
return useMemo(() => {
const timestamps = timestampsRef.current;
const arr = Array.from(messagesById.values());
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
arr.sort((a, b) => {
const ta = timestamps.get(a.id) ?? 0;
const tb = timestamps.get(b.id) ?? 0;
@@ -124,6 +124,7 @@ export function useDeliveriesLiveReload({
const location = useLocation();
const deliveriesPollFetcher = useTypedFetcher<typeof liveDeliveriesLoader>();
const deliveriesPollFetcherStateRef = useRef(deliveriesPollFetcher.state);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
deliveriesPollFetcherStateRef.current = deliveriesPollFetcher.state;
const [visibleDeliveries, setVisibleDeliveries] = useState(deliveries);
@@ -11,6 +11,7 @@ export function useAutoRevalidate(options: UseAutoRevalidateOptions = {}) {
const { interval = 5000, onFocus = true, disabled = false } = options;
const revalidator = useRevalidator();
const revalidatorRef = useRef(revalidator);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
revalidatorRef.current = revalidator;
useEffect(() => {
+2
View File
@@ -12,7 +12,9 @@ export function useChanged<T extends { id: string }>(
const itemRef = useRef<T | undefined>();
const itemId = item?.id;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
actionRef.current = action;
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
itemRef.current = item;
useEffect(() => {
@@ -207,6 +207,7 @@ export function useDashboardEditor({
const isInitializedRef = useRef(false);
const currentLayoutJsonRef = useRef<string>(JSON.stringify(initialData.layout));
const initialDataRef = useRef(initialData);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
initialDataRef.current = initialData;
// Sync queue to prevent race conditions
+1
View File
@@ -29,6 +29,7 @@ export function useDebounceEffect<T>(value: T, fn: (value: T) => void, delay: nu
const fnRef = useRef(fn);
// Update the ref whenever the function changes
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
fnRef.current = fn;
useEffect(() => {
@@ -8,6 +8,7 @@ export function useElementVisibility({ onVisibilityChange }: UseElementVisibilit
const ref = useRef<HTMLDivElement>(null);
const isVisibleRef = useRef(false);
const callbackRef = useRef(onVisibilityChange);
// oxlint-disable-next-line react/react-compiler -- This ref intentionally coordinates an imperative integration outside React state.
callbackRef.current = onVisibilityChange;
useEffect(() => {