mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
refactor(site/src/pages/AgentsPage): clean up manual ref-sync callback patterns (#23732)
This commit is contained in:
@@ -92,10 +92,7 @@ const AgentEmbedPage: FC = () => {
|
||||
const embedSessionMutation = useMutation(
|
||||
bootstrapChatEmbedSession({ checks: permissionChecks }, queryClient),
|
||||
);
|
||||
const latestEmbedSessionMutationRef = useRef(embedSessionMutation);
|
||||
useEffect(() => {
|
||||
latestEmbedSessionMutationRef.current = embedSessionMutation;
|
||||
});
|
||||
|
||||
const inFlightBootstrapRef = useRef<Promise<unknown> | null>(null);
|
||||
|
||||
const [chatErrorReasons, setChatErrorReasons] = useState<
|
||||
@@ -258,7 +255,7 @@ const AgentEmbedPage: FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const bootstrapPromise = latestEmbedSessionMutationRef.current
|
||||
const bootstrapPromise = embedSessionMutation
|
||||
.mutateAsync(token)
|
||||
.catch(() => undefined)
|
||||
.finally(() => {
|
||||
@@ -277,7 +274,7 @@ const AgentEmbedPage: FC = () => {
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, [agentId, isAwaitingBootstrapMessage]);
|
||||
}, [agentId, isAwaitingBootstrapMessage, embedSessionMutation]);
|
||||
|
||||
const handleBootstrapRetry = () => {
|
||||
inFlightBootstrapRef.current = null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { watchChat } from "#/api/api";
|
||||
import { chatMessagesKey, updateInfiniteChatsCache } from "#/api/queries/chats";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
import { asNumber, asString } from "#/components/ai-elements/runtimeTypeUtils";
|
||||
import { useEffectEvent } from "#/hooks/hookPolyfills";
|
||||
import type { OneWayMessageEvent } from "#/utils/OneWayWebSocket";
|
||||
import { createReconnectingWebSocket } from "#/utils/reconnectingWebSocket";
|
||||
import {
|
||||
@@ -686,12 +687,8 @@ export const useChatStore = (
|
||||
// can call them without including them in its dependency array.
|
||||
// This prevents the socket from tearing down when the parent
|
||||
// re-renders with new callback identities.
|
||||
const setChatErrorReasonRef = useRef(setChatErrorReason);
|
||||
const clearChatErrorReasonRef = useRef(clearChatErrorReason);
|
||||
useEffect(() => {
|
||||
setChatErrorReasonRef.current = setChatErrorReason;
|
||||
clearChatErrorReasonRef.current = clearChatErrorReason;
|
||||
}, [setChatErrorReason, clearChatErrorReason]);
|
||||
const setChatErrorReasonStable = useEffectEvent(setChatErrorReason);
|
||||
const clearChatErrorReasonStable = useEffectEvent(clearChatErrorReason);
|
||||
|
||||
// True once the initial REST page has resolved for the current
|
||||
// chat. The WebSocket effect gates on this so that
|
||||
@@ -1009,7 +1006,7 @@ export const useChatStore = (
|
||||
store.clearRetryState();
|
||||
}
|
||||
if (nextStatus !== "error") {
|
||||
clearChatErrorReasonRef.current(chatID);
|
||||
clearChatErrorReasonStable(chatID);
|
||||
}
|
||||
updateSidebarChat((chat) =>
|
||||
chat.status === nextStatus
|
||||
@@ -1026,7 +1023,7 @@ export const useChatStore = (
|
||||
store.setChatStatus("error");
|
||||
store.setStreamError(reason);
|
||||
store.clearRetryState();
|
||||
setChatErrorReasonRef.current(chatID, reason);
|
||||
setChatErrorReasonStable(chatID, reason);
|
||||
updateSidebarChat((chat) =>
|
||||
chat.status === "error" ? chat : { ...chat, status: "error" },
|
||||
);
|
||||
@@ -1099,7 +1096,14 @@ export const useChatStore = (
|
||||
}
|
||||
activeChatIDRef.current = null;
|
||||
};
|
||||
}, [chatID, initialDataLoaded, queryClient, store]);
|
||||
}, [
|
||||
chatID,
|
||||
initialDataLoaded,
|
||||
queryClient,
|
||||
store,
|
||||
setChatErrorReasonStable,
|
||||
clearChatErrorReasonStable,
|
||||
]);
|
||||
return {
|
||||
store,
|
||||
clearStreamError: () => {
|
||||
|
||||
@@ -610,7 +610,6 @@ const ScrollAnchoredContainer: FC<{
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
const isFetchingRef = useRef(isFetchingMoreMessages);
|
||||
const hasFetchedRef = useRef(false);
|
||||
const onFetchRef = useRef(onFetchMoreMessages);
|
||||
const autoScrollRef = useRef(true);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const pendingPrependRef = useRef<{
|
||||
@@ -629,8 +628,7 @@ const ScrollAnchoredContainer: FC<{
|
||||
if (isFetchingMoreMessages) {
|
||||
hasFetchedRef.current = true;
|
||||
}
|
||||
onFetchRef.current = onFetchMoreMessages;
|
||||
}, [isFetchingMoreMessages, onFetchMoreMessages]);
|
||||
}, [isFetchingMoreMessages]);
|
||||
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -672,7 +670,7 @@ const ScrollAnchoredContainer: FC<{
|
||||
contentWidth: contentRect.width,
|
||||
};
|
||||
}
|
||||
onFetchRef.current();
|
||||
onFetchMoreMessages();
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -700,7 +698,7 @@ const ScrollAnchoredContainer: FC<{
|
||||
observer.disconnect();
|
||||
observerRef.current = null;
|
||||
};
|
||||
}, [scrollContainerRef]);
|
||||
}, [scrollContainerRef, onFetchMoreMessages]);
|
||||
|
||||
// When a fetch completes, re-observe the sentinel to force
|
||||
// the IntersectionObserver to re-evaluate. The observer only
|
||||
|
||||
@@ -84,6 +84,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { useEffectEvent } from "#/hooks/hookPolyfills";
|
||||
import { useAuthenticated } from "#/hooks/useAuthenticated";
|
||||
import { UserDropdownContent } from "#/modules/dashboard/Navbar/UserDropdown/UserDropdownContent";
|
||||
import { useDashboard } from "#/modules/dashboard/useDashboard";
|
||||
@@ -1401,14 +1402,9 @@ const LoadMoreSentinel: FC<{
|
||||
isFetchingNextPage?: boolean;
|
||||
}> = ({ onLoadMore, isFetchingNextPage }) => {
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
const onLoadMoreRef = useRef(onLoadMore);
|
||||
|
||||
// Keep the callback ref in sync so the observer closure
|
||||
// always calls the latest onLoadMore without needing to
|
||||
// tear down and re-create the observer.
|
||||
useEffect(() => {
|
||||
onLoadMoreRef.current = onLoadMore;
|
||||
}, [onLoadMore]);
|
||||
const onLoadMoreStable = useEffectEvent(() => {
|
||||
onLoadMore?.();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Don't observe while a fetch is in progress. When the
|
||||
@@ -1424,15 +1420,15 @@ const LoadMoreSentinel: FC<{
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0]?.isIntersecting && onLoadMoreRef.current) {
|
||||
onLoadMoreRef.current();
|
||||
if (entries[0]?.isIntersecting) {
|
||||
onLoadMoreStable();
|
||||
}
|
||||
},
|
||||
{ threshold: 0 },
|
||||
);
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [isFetchingNextPage]);
|
||||
}, [isFetchingNextPage, onLoadMoreStable]);
|
||||
|
||||
return (
|
||||
<div ref={sentinelRef} className="flex items-center justify-center py-2">
|
||||
|
||||
@@ -122,13 +122,6 @@ export function useDesktopConnection({
|
||||
);
|
||||
},
|
||||
});
|
||||
// Stable ref so the effect can call the latest
|
||||
// syncRemoteClipboardToLocal without listing it as a
|
||||
// dependency (its identity may change across renders).
|
||||
const syncClipboardRef = useRef(syncRemoteClipboardToLocal);
|
||||
useEffect(() => {
|
||||
syncClipboardRef.current = syncRemoteClipboardToLocal;
|
||||
}, [syncRemoteClipboardToLocal]);
|
||||
|
||||
const attach = (container: HTMLElement) => {
|
||||
const screen = offscreenContainerRef.current;
|
||||
@@ -256,7 +249,7 @@ export function useDesktopConnection({
|
||||
return;
|
||||
}
|
||||
setRemoteClipboardText(text);
|
||||
syncClipboardRef.current(text).catch((err) => {
|
||||
syncRemoteClipboardToLocal(text).catch((err) => {
|
||||
console.error("Failed to sync remote clipboard to local:", err);
|
||||
});
|
||||
});
|
||||
@@ -500,7 +493,7 @@ export function useDesktopConnection({
|
||||
setStatus("idle");
|
||||
setHasConnected(false);
|
||||
};
|
||||
}, [chatId]);
|
||||
}, [chatId, syncRemoteClipboardToLocal]);
|
||||
|
||||
return {
|
||||
status,
|
||||
|
||||
Reference in New Issue
Block a user