motiondivision / motiondivision/motion
[BUG] `useTransform` value desyncs when source is set inside an effect under React 18 Strict Mode
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 33.7k
- Forks
- 1.4k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 14
Description
Summary
Updating a MotionValue inside a (layout) effect during Strict Mode's mount→unmount→remount cycle leaves a useTransform-derived value permanently stuck at its stale value.
Root cause
useCombineMotionValues recomputes via two paths: a sync updateValue() in the render body, and an async frame.preRender(updateValue) on the source's change, cancelled by cancelFrame in the effect cleanup.
Failing sequence under Strict Mode:
- First effect sets source →
change→updateValuescheduled to next frame (still stale). - Strict Mode cleanup runs
cancelFrame→ pending task cancelled before it flushes (remount is continuous, no frame in between). - Remount re-subscribes but doesn't re-render, so the sync fallback never runs again.
- Effect sets the same value →
setshort-circuits → nochange→ nothing re-scheduled.
Result: derived value stays at f(oldValue) while source is newValue.
React 19 behavior changes
This bug disappears in React 19:
In React 18, Strict Mode's mount→unmount→remount runs synchronously inside the commit phase, so the whole effect → cleanup → effect sequence finishes before any frame. Derived value's update task is canceled and never runs.
In React 19, the double-invoke was moved into the passive-effects phase. The order becomes effect → rAF → cleanup → effect. The derived value update task can run before cleanup so the bug disappears.
Reproduction
https://codesandbox.io/p/sandbox/snowy-river-qq5y9c
Expected vs actual
Expected: doubled equals 20. Actual: doubled equals 0, never recovers.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with packages/framer-motion/src/value/use-combine-values.ts at the linked lines 22-42, then reproduce the issue in the linked CodeSandbox under React 18 Strict Mode. Trace the effect, cleanup, frame scheduling, and remount sequence; done means the reproduction's derived value reaches 20 instead of remaining at 0 and stays synchronized.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100