fix(site): stabilize mutation callbacks for React Compiler memoization (#24089)

This commit is contained in:
Danielle Maywood
2026-04-07 15:05:27 +01:00
committed by GitHub
parent d7c8213eee
commit 0c5d189aff
+24 -18
View File
@@ -646,17 +646,24 @@ const AgentChatPage: FC = () => {
const isRegenerateTitleDisabled = isArchived || isRegeneratingThisChat;
const chatLastModelConfigID = chatRecord?.last_model_config_id;
const sendMutation = useMutation(
// Destructure mutation results directly so the React Compiler
// tracks stable primitives/functions instead of the whole result
// object (TanStack Query v5 recreates it every render via object
// spread). Keeping no intermediate variable prevents future code
// from accidentally closing over the unstable object.
const { isPending: isSendPending, mutateAsync: sendMessage } = useMutation(
createChatMessage(queryClient, agentId ?? ""),
);
const editMutation = useMutation(editChatMessage(queryClient, agentId ?? ""));
const interruptMutation = useMutation(
const { isPending: isEditPending, mutateAsync: editMessage } = useMutation(
editChatMessage(queryClient, agentId ?? ""),
);
const { isPending: isInterruptPending, mutateAsync: interrupt } = useMutation(
interruptChat(queryClient, agentId ?? ""),
);
const deleteQueuedMutation = useMutation(
const { mutateAsync: deleteQueuedMessage } = useMutation(
deleteChatQueuedMessage(queryClient, agentId ?? ""),
);
const promoteQueuedMutation = useMutation(
const { mutateAsync: promoteQueuedMessage } = useMutation(
promoteChatQueuedMessage(queryClient, agentId ?? ""),
);
@@ -754,9 +761,7 @@ const AgentChatPage: FC = () => {
hasUserFixableModelProviders,
});
const isSubmissionPending =
sendMutation.isPending ||
editMutation.isPending ||
interruptMutation.isPending;
isSendPending || isEditPending || isInterruptPending;
const isInputDisabled = !hasModelOptions || isArchived;
const handleUsageLimitError = (error: unknown): void => {
@@ -842,7 +847,7 @@ const AgentChatPage: FC = () => {
setPendingEditMessageId(editedMessageID);
scrollToBottomRef.current?.();
try {
await editMutation.mutateAsync({
await editMessage({
messageId: editedMessageID,
req: request,
});
@@ -873,9 +878,9 @@ const AgentChatPage: FC = () => {
// For queued sends the WebSocket status events handle
// clearing; for non-queued sends we clear explicitly
// below. Clearing eagerly causes a visible cutoff.
let response: Awaited<ReturnType<typeof sendMutation.mutateAsync>>;
let response: Awaited<ReturnType<typeof sendMessage>>;
try {
response = await sendMutation.mutateAsync(request);
response = await sendMessage(request);
} catch (error) {
handleUsageLimitError(error);
throw error;
@@ -908,10 +913,10 @@ const AgentChatPage: FC = () => {
};
const handleInterrupt = () => {
if (!agentId || interruptMutation.isPending) {
if (!agentId || isInterruptPending) {
return;
}
void interruptMutation.mutateAsync();
void interrupt();
};
const handleDeleteQueuedMessage = async (id: number) => {
@@ -920,7 +925,7 @@ const AgentChatPage: FC = () => {
previousQueuedMessages.filter((message) => message.id !== id),
);
try {
await deleteQueuedMutation.mutateAsync(id);
await deleteQueuedMessage(id);
} catch (error) {
store.setQueuedMessages(previousQueuedMessages);
throw error;
@@ -941,7 +946,7 @@ const AgentChatPage: FC = () => {
store.clearStreamError();
store.setChatStatus("pending");
try {
const promotedMessage = await promoteQueuedMutation.mutateAsync(id);
const promotedMessage = await promoteQueuedMessage(id);
// Insert the promoted message into the store and cache
// immediately so it appears in the timeline without
// waiting for the WebSocket to deliver it.
@@ -990,7 +995,8 @@ const AgentChatPage: FC = () => {
? `ssh ${workspaceAgent.name}.${workspace.name}.${workspace.owner_name}.${sshConfigQuery.data.hostname_suffix}`
: undefined;
const generateKeyMutation = useMutation({
// See mutation destructuring comment above (React Compiler).
const { mutate: generateKey } = useMutation({
mutationFn: () => API.getApiKey(),
});
@@ -1005,7 +1011,7 @@ const AgentChatPage: FC = () => {
const repoRoots = Array.from(gitWatcher.repositories.keys()).sort();
const folder = repoRoots[0] ?? workspaceAgent.expanded_directory;
generateKeyMutation.mutate(undefined, {
generateKey(undefined, {
onSuccess: ({ key }) => {
location.href = getVSCodeHref(editor, {
owner: workspace.owner_name,
@@ -1141,7 +1147,7 @@ const AgentChatPage: FC = () => {
compressionThreshold={compressionThreshold}
isInputDisabled={isInputDisabled}
isSubmissionPending={isSubmissionPending}
isInterruptPending={interruptMutation.isPending}
isInterruptPending={isInterruptPending}
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebarCollapsed={onToggleSidebarCollapsed}
showSidebarPanel={showSidebarPanel}