Skip to content

fix(core): stop superseded loop chains on re-render#2471

Merged
joshuaellis merged 1 commit into
nextfrom
fix/issue-1193
May 22, 2026
Merged

fix(core): stop superseded loop chains on re-render#2471
joshuaellis merged 1 commit into
nextfrom
fix/issue-1193

Conversation

@joshuaellis

Copy link
Copy Markdown
Member

Closes #1193.

Summary

Toggling loop from truthy to falsy on a re-render did not actually stop the animation. The in-flight flushUpdate chain captured its own loop value in a closure, so the captured true kept the recursion alive even when the new top-level update arrived with loop: false. Both chains then ran in parallel — the old one scheduled iterations indefinitely, the new one was a no-op.

Fix

Controller now carries a _lastLoopId generation token. A top-level external update bumps the token only when it could express a different loop intent (i.e. it mentions loop and is not an internal sub-step from runAsync's animate, which carries parentId, nor a pause/resume lifecycle call). Each loop chain captures its token; before recursing into the next iteration it checks the token still matches _lastLoopId and otherwise exits.

This is the minimal scoping required to avoid regressions in the three existing loop tests that rely on runAsync sub-steps and pause/resume cycles preserving the chain.

A regression test mirroring the reported shape (loop: true → loop: false via a second ctrl.start) is added in Controller.test.ts.

A re-render that flipped `loop` from truthy to falsy did not stop the
animation, because the in-flight `flushUpdate` chain captured its own
`loop` value and kept scheduling iterations alongside the new update.
The Controller now tags each loop chain with a generation token and
exits the recursion when a newer external update has superseded it.

Closes #1193.
@changeset-bot

changeset-bot Bot commented May 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1e04dc1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@react-spring/core Patch
@react-spring/animated Patch
@react-spring/mock-raf Patch
@react-spring/parallax Patch
@react-spring/rafz Patch
@react-spring/shared Patch
@react-spring/types Patch
@react-spring/konva Patch
@react-spring/native Patch
@react-spring/three Patch
@react-spring/web Patch
@react-spring/zdog Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented May 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-spring Ready Ready Preview May 22, 2026 7:48am

Request Review

@joshuaellis
joshuaellis merged commit 8e9876f into next May 22, 2026
18 checks passed
@joshuaellis
joshuaellis deleted the fix/issue-1193 branch May 22, 2026 07:53
This was referenced May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v9] Change spring config based on local state

1 participant