Skip to content

fix(web): prevent infinite render loop on /apps page#35393

Open
lyzno1 wants to merge 1 commit intolanggenius:mainfrom
lyzno1:fix/web-apps-list-infinite-loop
Open

fix(web): prevent infinite render loop on /apps page#35393
lyzno1 wants to merge 1 commit intolanggenius:mainfrom
lyzno1:fix/web-apps-list-infinite-loop

Conversation

@lyzno1
Copy link
Copy Markdown
Member

@lyzno1 lyzno1 commented Apr 19, 2026

Summary

Stabilize the pages reference derived from the apps infinite query so the downstream appIds memo, refreshWorkflowOnlineUsers callback, and the effect that consumes it stop re-running on every render. Without this, opening /apps on a fresh load can trip React's Maximum update depth exceeded guard.

Root cause

web/app/components/apps/list.tsx had a stale-but-latent line:

const pages = data?.pages ?? []

When useInfiniteAppList has not resolved yet, data?.pages ?? [] produces a brand-new empty array on every render. That fresh reference cascaded:

  1. appIds = useMemo(..., [pages]) — recomputes, returns a new array.
  2. refreshWorkflowOnlineUsers = useCallback(..., [appIds, ...]) — new function identity each render.
  3. useEffect(() => void refreshWorkflowOnlineUsers(), [refreshWorkflowOnlineUsers]) — re-runs every render.
  4. The effect calls setWorkflowOnlineUsersMap({}) with a brand-new {} literal, which React cannot bail out on (Object.is({}, {}) === false).
  5. setState triggers a re-render → goes back to step 1 → React's max-update-depth guard fires.

The pages = data?.pages ?? [] line itself has existed since #29004 (2025-12-02) and was harmless because nothing read it reactively. The collaboration feature in #30781 (2026-04-16) introduced the setState-in-effect chain that depends transitively on pages, which is what made the latent reference instability fatal.

Fix

Memoize pages so that the empty fallback keeps a stable reference across renders:

const pages = useMemo(() => data?.pages ?? [], [data?.pages])

This breaks the loop by stabilizing the entire downstream chain. No behavior change in the resolved-data path (TanStack Query already returns a stable data.pages reference once data is loaded).

Test plan

  • Open /apps on a fresh dev server (pnpm dev) — no Maximum update depth exceeded console error.
  • App list still loads, paginates via the intersection observer, and refreshes every 10 s when collaboration mode is enabled.
  • enable_collaboration_mode = false path still clears workflowOnlineUsersMap exactly once.
  • Lint / typecheck pass.

Notes / follow-ups (out of scope)

The workflowOnlineUsersMap block (useState + useEffect + setInterval + manual fetch) is essentially server state being managed as client state. A cleaner long-term fix is to migrate it to a useQuery({ queryKey: ['workflowOnlineUsers', appIds], refetchInterval: 10_000 }), which would eliminate the entire dependency chain that this PR stabilizes. Filed mentally for a follow-up; this PR keeps the surface minimal.

Made with Cursor

Stabilize the `pages` reference derived from the infinite query result so
that the downstream `appIds` memo, `refreshWorkflowOnlineUsers` callback
and the effect that calls it stop re-running on every render.

Before this change, `data?.pages ?? []` produced a fresh empty array on
every render whenever the query had not yet resolved. That fresh
reference cascaded into a new `appIds` array, a new callback identity and
a re-running effect that called `setWorkflowOnlineUsersMap({})` with a
new object literal each time, eventually tripping React's
"Maximum update depth exceeded" guard on `/apps`.

The line itself is four months old; it became reachable only after the
collaboration feature added the `setState`-in-effect chain that depends
transitively on `pages`.

Made-with: Cursor
@lyzno1 lyzno1 requested review from JzoNgKVO and iamjoel as code owners April 19, 2026 11:24
@dosubot dosubot bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Apr 19, 2026
@github-actions github-actions bot added the web This relates to changes on the web. label Apr 19, 2026
@dosubot dosubot bot added the javascript Pull requests that update javascript code label Apr 19, 2026
@lyzno1 lyzno1 enabled auto-merge April 19, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code size:XS This PR changes 0-9 lines, ignoring generated files. web This relates to changes on the web.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant