motiondivision / motiondivision/motion

[BUG] `useTransform` value desyncs when source is set inside an effect under React 18 Strict Mode

Open
#3,769 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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.

https://github.com/motiondivision/motion/blob/61833240e899fbbe4f50484ec5f9f7fe688de843/packages/framer-motion/src/value/use-combine-values.ts#L22-L42

Failing sequence under Strict Mode:

  1. First effect sets source → changeupdateValue scheduled to next frame (still stale).
  2. Strict Mode cleanup runs cancelFrame → pending task cancelled before it flushes (remount is continuous, no frame in between).
  3. Remount re-subscribes but doesn't re-render, so the sync fallback never runs again.
  4. Effect sets the same valueset short-circuits → no change → 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.