Skip to content

fix: prevent resize drift caused by stale props between renders#255

Merged
STRML merged 1 commit into
react-grid-layout:masterfrom
WhiteMinds:fix/resize-drift-stale-props
Feb 28, 2026
Merged

fix: prevent resize drift caused by stale props between renders#255
STRML merged 1 commit into
react-grid-layout:masterfrom
WhiteMinds:fix/resize-drift-stale-props

Conversation

@WhiteMinds
Copy link
Copy Markdown
Contributor

@WhiteMinds WhiteMinds commented Feb 27, 2026

Problem

When dragging a resize handle quickly, the element resizes progressively slower than the mouse cursor. The further you drag, the bigger the offset becomes.

Root Cause

In resizeHandler, the new size is computed as:

let width = this.props.width + deltaX / this.props.transformScale;

this.props.width depends on the parent re-rendering with updated props. When React can't re-render between consecutive mousemove events (common during fast dragging), multiple events share the same stale props.width:

Render #1: props.width = 200
  mousemove A: delta=3 → width = 200+3 = 203 → callback(203)
  mousemove B: delta=2 → width = 200+2 = 202 → callback(202)  ← overwrites 203
Render #2: props.width = 202 (3px delta from event A is lost)

This issue was already recognized for onResizeStop in #250 / a09f782, which introduced this.lastSize to avoid stale props. However the same stale-props problem also affects onResize during the drag.

Fix

Use this.lastSize (already tracked) as the base for delta calculation during onResize, falling back to this.props for the first event when lastSize is not yet set:

const baseWidth = this.lastSize?.width ?? this.props.width;
let width = baseWidth + (canDragX ? deltaX / this.props.transformScale : 0);

This is a 2-line change that extends the existing lastSize mechanism to cover the full resize lifecycle, not just the stop event.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced resize accuracy and responsiveness during continuous dragging operations. The resizable component now more effectively tracks size changes across consecutive drag events, eliminating movement data loss and stuttering. Users will experience noticeably smoother and more consistent resizing behavior, particularly during rapid or sustained resize interactions.

When React can't re-render between consecutive mousemove events,
this.props.width is stale and intermediate deltas are lost, causing
progressive drift where the element resizes slower than the mouse.

Use this.lastSize (already tracked for onResizeStop) as the base for
delta calculation during onResize, falling back to props for the first
event.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edc7cbd and f9ed729.

📒 Files selected for processing (1)
  • lib/Resizable.js

📝 Walkthrough

Walkthrough

The change updates resize calculation logic in Resizable.js to use lastSize as a fallback source for base dimensions during drag events, improving handling of batched React re-renders while maintaining constraint application and stop event behavior.

Changes

Cohort / File(s) Summary
Resize calculation optimization
lib/Resizable.js
Updated width/height resize calculations to use lastSize as fallback for base dimensions, reducing delta loss during consecutive drag events in batched React re-renders. Documentation comment and onResizeStop logic updated accordingly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 When drags cascade and batches blend,
We grasp the lastSize, a faithful friend,
No delta lost to React's swift flow,
Just smooth resizing, to and fro!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: prevent resize drift caused by stale props between renders' directly summarizes the main change—using lastSize instead of stale props to prevent resize drift during consecutive drag events.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@STRML STRML merged commit b42f2c7 into react-grid-layout:master Feb 28, 2026
2 of 4 checks passed
@STRML
Copy link
Copy Markdown
Collaborator

STRML commented Feb 28, 2026

Thanks much.

@manbearwiz
Copy link
Copy Markdown
Contributor

@STRML do you have an idea of when this might be released? We are also running into this issue.

STRML added a commit that referenced this pull request May 11, 2026
PR #255 switched the resize delta base from this.props.width/height to
the accumulated lastSize, but dimensionsChanged still compared against
props. When the net delta was zero (e.g., handle movement compensates
the drag), the callback would still fire because lastSize had drifted
from the unchanging props, and the multi-call test expectations no
longer matched.

Compare against baseWidth/baseHeight so suppression works correctly
under accumulation, and update the multi-call test expectations to
reflect that subsequent calls accumulate from lastSize.

Also bumps devDependencies (eslint, jest, react, babel, webpack). Note
that eslint 10 is incompatible with @babel/eslint-parser 7.28.x at the
moment — yarn lint will error until the parser catches up; yarn test is
unaffected. Committed with --no-verify because the pre-commit lint hook
fails for this reason.
@STRML
Copy link
Copy Markdown
Collaborator

STRML commented May 11, 2026

@manbearwiz @3.2.0

STRML added a commit that referenced this pull request May 12, 2026
- CHANGELOG.md: add 4.0.0 entry covering the Flow->TS migration. Mark the
  types-delivery change as breaking and point Flow users at the last
  Flow-annotated source by SHA (db2e37e, the 3.2.0 tag). Backfill the
  missing 3.2.0 entry (PR #255 fix + ESLint pin).
- README.md: new TypeScript section (bundled types ship from build/*.d.ts,
  remove @types/react-resizable). Flow-removal note with the same SHA link.
  Refresh lib/*.js references to *.tsx. Rewrite the Flow-flavored Props
  block in TypeScript. Compatibility table gains a Types column.
- CI (.github/workflows/test.yml): now runs lint, typecheck, test, build,
  and a build-artifact smoke test on every push and PR. Bump checkout/
  setup-node to v4 and add yarn caching.
- __tests__/integration/smoke.cjs: plain CJS smoke test for the published
  shape — root entry resolves, named exports are constructible, defaultProps
  and propTypes preserved, bundled .d.ts files exist with expected symbols,
  legacy require()() guard throws, files whitelist correct.
- __tests__/integration/end-to-end.test.tsx: 32 Jest integration tests
  driving real DOM events through DraggableCore. Same suite runs twice —
  against the TS source and against the built CJS root entry (gated on
  build/ existing). Covers drag-driven size changes, controlled-mode
  callbacks, the #255 regression (mid-drag prop staleness), zero-delta
  suppression, min/max constraints, lockAspectRatio, axis restrictions,
  transformScale, all 8 handle axes, and custom-function handles.

Coverage on Resizable.tsx is now 98.76% lines, ResizableBox.tsx 100%.
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.

3 participants