Skip to content

Commit 43cca67

Browse files
committed
chore: enforce exhaustive React hook dependencies
1 parent 23c5619 commit 43cca67

72 files changed

Lines changed: 882 additions & 775 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.oxlintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"typescript/consistent-type-imports": "error",
4646
"import/no-duplicates": "error",
4747
"import/namespace": "off",
48-
"react/exhaustive-deps": "off",
48+
"react/exhaustive-deps": "error",
4949
"react/rules-of-hooks": "off",
5050
"guard-for-in": "error",
5151
"symbol-description": "error",

apps/webapp/app/assets/icons/AnimatedHourglassIcon.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function AnimatedHourglassIcon({
1212
const [scope, animate] = useAnimate();
1313

1414
useEffect(() => {
15-
animate(
15+
const controls = animate(
1616
[
1717
[scope.current, { rotate: 0 }, { duration: 0.7 }],
1818
[scope.current, { rotate: 180 }, { duration: 0.3 }],
@@ -21,7 +21,9 @@ export function AnimatedHourglassIcon({
2121
],
2222
{ repeat: Infinity, delay }
2323
);
24-
}, []);
24+
25+
return () => controls.stop();
26+
}, [animate, delay, scope]);
2527

2628
return <HourglassIcon ref={scope} className={className} />;
2729
}

apps/webapp/app/components/AskAI.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function useAskAIState() {
7676
next.delete(ASK_AI_DEEP_LINK_PARAM);
7777
setSearchParams(next);
7878
}
79-
}, [searchParams, openAskAI]);
79+
}, [searchParams, setSearchParams, openAskAI]);
8080

8181
return { isOpen, setIsOpen, initialQuery, openAskAI, closeAskAI };
8282
}

apps/webapp/app/components/DevPresence.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export function DevPresenceProvider({ children, enabled = true }: DevPresencePro
8080
// Calculate isConnected and memoize the context value
8181
const contextValue = useMemo(() => {
8282
return { isConnected };
83-
}, [isConnected, enabled]);
83+
}, [isConnected]);
8484

8585
return <DevPresenceContext.Provider value={contextValue}>{children}</DevPresenceContext.Provider>;
8686
}

apps/webapp/app/components/Feedback.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function Feedback({
7070
) {
7171
setOpen(false);
7272
}
73-
}, [navigation.formAction, navigation.state, form.allErrors]);
73+
}, [navigation.formAction, navigation.state, form.allErrors, setOpen]);
7474

7575
// Handle URL param functionality
7676
useEffect(() => {
@@ -83,7 +83,7 @@ export function Feedback({
8383
next.delete("feedbackPanel");
8484
setSearchParams(next);
8585
}
86-
}, [searchParams]);
86+
}, [searchParams, setOpen, setSearchParams]);
8787

8888
// Reset the topic to the default once the dialog closes, so reopening always starts fresh. The
8989
// dialog is now persistently mounted (hosted outside the popover), so without this it would keep

apps/webapp/app/components/admin/FeatureFlagsDialog.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useFetcher } from "@remix-run/react";
2-
import { useEffect, useState } from "react";
2+
import { useEffect, useRef, useState } from "react";
33
import stableStringify from "json-stable-stringify";
44
import {
55
Dialog,
@@ -54,6 +54,9 @@ export function FeatureFlagsDialog({
5454
}: FeatureFlagsDialogProps) {
5555
const loadFetcher = useFetcher<LoaderData>();
5656
const saveFetcher = useFetcher<ActionData>();
57+
const loadFeatureFlags = loadFetcher.load;
58+
const onOpenChangeRef = useRef(onOpenChange);
59+
onOpenChangeRef.current = onOpenChange;
5760

5861
const [overrides, setOverrides] = useState<Record<string, unknown>>({});
5962
const [initialOverrides, setInitialOverrides] = useState<Record<string, unknown>>({});
@@ -67,9 +70,9 @@ export function FeatureFlagsDialog({
6770
setSaveError(null);
6871
setOverrides({});
6972
setInitialOverrides({});
70-
loadFetcher.load(`/admin/api/v2/orgs/${orgId}/feature-flags`);
73+
loadFeatureFlags(`/admin/api/v2/orgs/${orgId}/feature-flags`);
7174
}
72-
}, [open, orgId]);
75+
}, [loadFeatureFlags, open, orgId]);
7376

7477
useEffect(() => {
7578
if (loadFetcher.data) {
@@ -81,7 +84,7 @@ export function FeatureFlagsDialog({
8184

8285
useEffect(() => {
8386
if (saveFetcher.data?.success) {
84-
onOpenChange(false);
87+
onOpenChangeRef.current(false);
8588
} else if (saveFetcher.data?.error) {
8689
setSaveError(saveFetcher.data.error);
8790
}

apps/webapp/app/components/admin/debugRun.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ function DebugRunDialog({ friendlyId }: { friendlyId: string }) {
4545
function DebugRunContent({ friendlyId }: { friendlyId: string }) {
4646
const fetcher = useTypedFetcher<typeof loader>();
4747
const isLoading = fetcher.state === "loading";
48+
const load = fetcher.load;
4849

4950
useEffect(() => {
50-
fetcher.load(`/resources/taskruns/${friendlyId}/debug`);
51-
}, [friendlyId]);
51+
load(`/resources/taskruns/${friendlyId}/debug`);
52+
}, [friendlyId, load]);
5253

5354
return (
5455
<>

apps/webapp/app/components/code/AIQueryInput.tsx

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,39 @@ export function AIQueryInput({
6565
}
6666
}, [mode, canEdit]);
6767

68+
const processStreamEvent = useCallback(
69+
(event: StreamEventType) => {
70+
switch (event.type) {
71+
case "thinking":
72+
setThinking((prev) => prev + event.content);
73+
break;
74+
case "tool_call":
75+
// Tool calls are handled silently — no UI text needed
76+
break;
77+
case "time_filter":
78+
// Apply time filter immediately when the AI sets it
79+
onTimeFilterChange?.(event.filter);
80+
break;
81+
case "result":
82+
if (event.success) {
83+
// Apply time filter if included in result (backup in case time_filter event was missed)
84+
if (event.timeFilter) {
85+
onTimeFilterChange?.(event.timeFilter);
86+
}
87+
onQueryGenerated(event.query);
88+
setPrompt("");
89+
setLastResult("success");
90+
// Keep thinking visible to show what happened
91+
} else {
92+
setError(event.error);
93+
setLastResult("error");
94+
}
95+
break;
96+
}
97+
},
98+
[onQueryGenerated, onTimeFilterChange]
99+
);
100+
68101
const submitQuery = useCallback(
69102
async (queryPrompt: string, submitMode: AIQueryMode = mode) => {
70103
if (!queryPrompt.trim() || isLoading) return;
@@ -158,40 +191,7 @@ export function AIQueryInput({
158191
setIsLoading(false);
159192
}
160193
},
161-
[isLoading, resourcePath, mode, getCurrentQuery]
162-
);
163-
164-
const processStreamEvent = useCallback(
165-
(event: StreamEventType) => {
166-
switch (event.type) {
167-
case "thinking":
168-
setThinking((prev) => prev + event.content);
169-
break;
170-
case "tool_call":
171-
// Tool calls are handled silently — no UI text needed
172-
break;
173-
case "time_filter":
174-
// Apply time filter immediately when the AI sets it
175-
onTimeFilterChange?.(event.filter);
176-
break;
177-
case "result":
178-
if (event.success) {
179-
// Apply time filter if included in result (backup in case time_filter event was missed)
180-
if (event.timeFilter) {
181-
onTimeFilterChange?.(event.timeFilter);
182-
}
183-
onQueryGenerated(event.query);
184-
setPrompt("");
185-
setLastResult("success");
186-
// Keep thinking visible to show what happened
187-
} else {
188-
setError(event.error);
189-
setLastResult("error");
190-
}
191-
break;
192-
}
193-
},
194-
[onQueryGenerated, onTimeFilterChange]
194+
[getCurrentQuery, isLoading, mode, processStreamEvent, resourcePath]
195195
);
196196

197197
const handleSubmit = useCallback(

apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export function DashboardAgent({
281281
cancelled = true;
282282
stop();
283283
};
284-
}, [hasAccess, watching, actionPath, setPanelOpen, openChat]);
284+
}, [hasAccess, watching, actionPath, setPanelOpen, openChat, rememberToasted]);
285285

286286
// Zeroes the wake dot right away; the poll restores the truth if another chat has one. The
287287
// work count is not touched here: the panel derives it from the chat list.

apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ export function DashboardAgentPanel({
476476
watchCard.requestId,
477477
active?.chatId,
478478
actionPath,
479+
organization.id,
479480
claimChatSlot,
480481
loadHistory,
481482
]);

0 commit comments

Comments
 (0)