From 0b6c1e4165fd73cbb823d64b1b15ca0d49096435 Mon Sep 17 00:00:00 2001 From: Iuliia Ivashko Date: Tue, 3 Mar 2026 16:42:36 +0200 Subject: [PATCH] fix(git): stop PR section refresh loop causing React #185 (#588) Prevent a feedback loop between remote selection and refresh effects in PullRequestSection. This keeps refresh callback dependencies stable and avoids repeated state updates that triggered maximum update depth errors in desktop Git PR view. --- .../views/git/PullRequestSection.tsx | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/views/git/PullRequestSection.tsx b/packages/ui/src/components/views/git/PullRequestSection.tsx index aeaacf17..1a24bea5 100644 --- a/packages/ui/src/components/views/git/PullRequestSection.tsx +++ b/packages/ui/src/components/views/git/PullRequestSection.tsx @@ -416,6 +416,7 @@ export const PullRequestSection: React.FC<{ const lastRefreshAtRef = React.useRef(0); const lastDiscoveryPollAtRef = React.useRef(0); const statusRef = React.useRef(null); + const selectedRemoteNameRef = React.useRef(selectedRemote?.name ?? null); const attemptedBodyHydrationRef = React.useRef>(new Set()); const lastSyncedPrNumberRef = React.useRef(null); @@ -926,6 +927,10 @@ export const PullRequestSection: React.FC<{ statusRef.current = status; }, [status]); + React.useEffect(() => { + selectedRemoteNameRef.current = selectedRemote?.name ?? null; + }, [selectedRemote?.name]); + const refresh = React.useCallback(async (options?: { force?: boolean; onlyExistingPr?: boolean; silent?: boolean; markInitialResolved?: boolean }) => { if (!canShow) return; if (options?.onlyExistingPr && !statusRef.current?.pr) { @@ -967,7 +972,7 @@ export const PullRequestSection: React.FC<{ } setError(null); try { - const next = await github.prStatus(directory, branch, selectedRemote?.name); + const next = await github.prStatus(directory, branch, selectedRemoteNameRef.current ?? undefined); setStatus((prev) => { const nextPr = next.pr; const prevPr = prev?.pr; @@ -1014,11 +1019,11 @@ export const PullRequestSection: React.FC<{ } isRefreshInFlightRef.current = false; } - }, [branch, canShow, directory, github, githubAuthChecked, githubAuthStatus, selectedRemote?.name]); + }, [branch, canShow, directory, github, githubAuthChecked, githubAuthStatus]); // Refetch PR status when selected remote changes const handleRemoteChange = React.useCallback((remote: GitRemote) => { - setSelectedRemote(remote); + setSelectedRemote((prev) => (prev?.name === remote.name ? prev : remote)); // Clear current status and refetch setStatus(null); setError(null); @@ -1032,24 +1037,26 @@ export const PullRequestSection: React.FC<{ setBody(snapshot?.body ?? ''); setDraft(snapshot?.draft ?? false); setTargetBaseBranch(snapshot?.targetBaseBranch ? normalizeBranchRef(snapshot.targetBaseBranch) : normalizeBranchRef(baseBranch)); - setSelectedRemote( - pickInitialPrRemote(remotes, { - selectedRemoteName: snapshot?.selectedRemoteName, - trackingBranch, - }) - ); + const nextRemote = pickInitialPrRemote(remotes, { + selectedRemoteName: snapshot?.selectedRemoteName, + trackingBranch, + }); + setSelectedRemote((prev) => (prev?.name === nextRemote?.name ? prev : nextRemote)); setStatus(statusSnapshot); setError(null); setIsInitialStatusResolved(Boolean(statusSnapshot)); + }, [baseBranch, branch, remotes, snapshotKey, trackingBranch]); + + React.useEffect(() => { void refresh({ force: true, markInitialResolved: true }); - }, [baseBranch, branch, refresh, remotes, snapshotKey, trackingBranch]); + }, [snapshotKey, refresh]); // Refetch when selected remote changes React.useEffect(() => { - if (selectedRemote) { + if (selectedRemote?.name) { void refresh({ force: true, markInitialResolved: true }); } - }, [selectedRemote, refresh]); + }, [selectedRemote?.name, refresh]); React.useEffect(() => { const onFocus = () => {