Skip to content

[Image] | (fix) | Batch setState calls in setupGraphie to prevent infinite loop#3329

Open
Myranae wants to merge 9 commits intomainfrom
tb/LEMS-3853/max-update-depth-error
Open

[Image] | (fix) | Batch setState calls in setupGraphie to prevent infinite loop#3329
Myranae wants to merge 9 commits intomainfrom
tb/LEMS-3853/max-update-depth-error

Conversation

@Myranae
Copy link
Contributor

@Myranae Myranae commented Mar 11, 2026

Problem

SvgImage.setupGraphie called setState once per label inside a loop. Because this.state doesn't update synchronously between setState calls in class components, each iteration spread the same stale base — leaving labelsRendered in an inconsistent intermediate state. An async prop change from ImageComponent's zoomSize effect would land mid-loop, bypassing shouldComponentUpdate and exposing Graphie to the bad state. Graphie interpreted this as a meaningful options change, re-ran setupGraphie, and triggered another setState storm — repeating until React hit its update depth limit.

Solution

Accumulate all label updates into a plain object during the loop and call setState once. The state transition is now atomic, eliminating intermediate inconsistent states and the re-render storm they caused. The updater also uses a functional form (prev => ...) so the merge always reads from the latest committed state rather than this.state at call time.

Issue: LEMS-3853

Test plan:

  • Confirm all CI checks pass
  • Confirm Graphies in image widgets function as before (no regressions) using a FE test branch
  • Confirm the Sentry issue linked in the ticket stops producing new events after deploy
  • Verify on mobile (the zoomSize effect only fires when scaleFF is off, and mobile is the most common path
    where that matters)

Completed with Claude Code

@Myranae Myranae self-assigned this Mar 11, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

🗄️ Schema Change: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Size Change: +18 B (0%)

Total Size: 486 kB

Filename Size Change
packages/perseus/dist/es/index.js 187 kB +18 B (+0.01%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.8 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 5.96 kB
packages/math-input/dist/es/index.js 98.5 kB
packages/math-input/dist/es/strings.js 1.61 kB
packages/perseus-core/dist/es/index.item-splitting.js 11.8 kB
packages/perseus-core/dist/es/index.js 24.9 kB
packages/perseus-editor/dist/es/index.js 100 kB
packages/perseus-linter/dist/es/index.js 8.82 kB
packages/perseus-score/dist/es/index.js 9.26 kB
packages/perseus-utils/dist/es/index.js 403 B
packages/perseus/dist/es/strings.js 7.47 kB
packages/pure-markdown/dist/es/index.js 1.39 kB
packages/simple-markdown/dist/es/index.js 6.71 kB

compressed-size-action

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

🛠️ Item Splitting: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (854b70a) and published it to npm. You
can install it using the tag PR3329.

Example:

pnpm add @khanacademy/perseus@PR3329

If you are working in Khan Academy's frontend, you can run the below command.

./dev/tools/bump_perseus_version.ts -t PR3329

If you are working in Khan Academy's webapp, you can run the below command.

./dev/tools/bump_perseus_version.js -t PR3329

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove this before landing!

Myranae added 3 commits March 12, 2026 09:32
…Graphie setState

Reads from `prev` instead of `this.state` to guarantee the latest state is used, avoiding potential staleness if `setupGraphie` were ever called while a setState is still pending.
@Myranae Myranae marked this pull request as ready for review March 12, 2026 16:48
@Myranae Myranae requested review from a team, ivyolamit, jeremywiebe and nishasy March 12, 2026 16:48
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward batching of setState calls to fix infinite loop.

Extended reasoning...

Overview

This PR fixes a React "Maximum update depth exceeded" error (LEMS-3853) by batching setState calls in SvgImage.setupGraphie. The only meaningful code change is ~10 lines in packages/perseus/src/components/svg-image.tsx: moving per-label setState calls out of a loop into a single batched call with a functional updater (prev) => .... The other files are a changeset and an investigation plan doc (author noted it will be removed before landing).

Security risks

None. The change is purely a React state management fix — no user input handling, auth, or data exposure changes.

Level of scrutiny

Low scrutiny needed. The fix applies a well-established React pattern (accumulate updates, single setState with functional updater). The before/after behavior is easy to reason about, and the PR description thoroughly explains the root cause. The change is small and self-contained.

Other factors

No bugs were found by the automated bug hunting system. The author has an outstanding note to remove .claude/plans/lems-3853-summary.md before landing, but that is a non-code housekeeping item that does not affect correctness. The bundle size impact is negligible (+18 B).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant