mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-30 09:21:33 +00:00
fix(projects-state): stop websocket message reprocessing loop
The websocket projects effect in useProjectsState could re-handle the same latestMessage after local state writes triggered re-renders. Under bursty websocket traffic, this created an update feedback cycle that surfaced as 'Maximum update depth exceeded', often from Sidebar. What changed: - Added lastHandledMessageRef so each latestMessage object is handled once. - Added an early return guard when the current message was already handled. - Made projects updates idempotent by comparing previous and merged payloads before calling setProjects. Result: - Breaks the effect -> state update -> effect re-entry cycle. - Reduces redundant renders during rapid projects_updated traffic while preserving normal project/session synchronization.
This commit is contained in:
@@ -183,6 +183,7 @@ export function useProjectsState({
|
||||
const [externalMessageUpdate, setExternalMessageUpdate] = useState(0);
|
||||
|
||||
const loadingProgressTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const lastHandledMessageRef = useRef<AppSocketMessage | null>(null);
|
||||
|
||||
const fetchProjects = useCallback(async ({ showLoadingState = true }: FetchProjectsOptions = {}) => {
|
||||
try {
|
||||
@@ -288,6 +289,15 @@ export function useProjectsState({
|
||||
return;
|
||||
}
|
||||
|
||||
// `latestMessage` is event-like data. This effect also depends on local state
|
||||
// (`projects`, `selectedProject`, `selectedSession`) to compute derived updates.
|
||||
// Without this guard, handling one websocket message can update that local
|
||||
// state, retrigger the effect, and re-handle the same websocket message.
|
||||
if (lastHandledMessageRef.current === latestMessage) {
|
||||
return;
|
||||
}
|
||||
lastHandledMessageRef.current = latestMessage;
|
||||
|
||||
if (latestMessage.type === 'loading_progress') {
|
||||
if (loadingProgressTimeoutRef.current) {
|
||||
clearTimeout(loadingProgressTimeoutRef.current);
|
||||
@@ -335,7 +345,9 @@ export function useProjectsState({
|
||||
return;
|
||||
}
|
||||
|
||||
setProjects(updatedProjects);
|
||||
setProjects((previousProjects) =>
|
||||
projectsHaveChanges(previousProjects, updatedProjects, true) ? updatedProjects : previousProjects,
|
||||
);
|
||||
|
||||
if (!selectedProject) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user