mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-01 10:18:37 +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 [externalMessageUpdate, setExternalMessageUpdate] = useState(0);
|
||||||
|
|
||||||
const loadingProgressTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const loadingProgressTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const lastHandledMessageRef = useRef<AppSocketMessage | null>(null);
|
||||||
|
|
||||||
const fetchProjects = useCallback(async ({ showLoadingState = true }: FetchProjectsOptions = {}) => {
|
const fetchProjects = useCallback(async ({ showLoadingState = true }: FetchProjectsOptions = {}) => {
|
||||||
try {
|
try {
|
||||||
@@ -288,6 +289,15 @@ export function useProjectsState({
|
|||||||
return;
|
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 (latestMessage.type === 'loading_progress') {
|
||||||
if (loadingProgressTimeoutRef.current) {
|
if (loadingProgressTimeoutRef.current) {
|
||||||
clearTimeout(loadingProgressTimeoutRef.current);
|
clearTimeout(loadingProgressTimeoutRef.current);
|
||||||
@@ -335,7 +345,9 @@ export function useProjectsState({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProjects(updatedProjects);
|
setProjects((previousProjects) =>
|
||||||
|
projectsHaveChanges(previousProjects, updatedProjects, true) ? updatedProjects : previousProjects,
|
||||||
|
);
|
||||||
|
|
||||||
if (!selectedProject) {
|
if (!selectedProject) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user