perf: Drive active item position from a single reaction - #586
Merged
Conversation
During a drag activeItemPosition updates every frame and each item's layout reaction subscribed to it, so moving one item did O(N) work per frame. Each item now registers its position mutable and a single reaction in CommonValuesProvider drives only the active item's mutable, so inactive items do no per-frame work. The drag lag appeared after the reanimated 4.5 / worklets 0.10 / RN 0.86 bump; the library source was unchanged. Still needs on-device drag verification.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
calculateLayout allocates fresh Vector objects for every item on each reorder, so each item's layoutPosition derived value and inactive-item updater fired even when the item did not move. Reuse the previous Vector reference for keys whose value is unchanged so those per-item reactions short-circuit on reference equality, dropping reorder cost to the number of items that actually moved. Also trims the verbose comments added in the previous commit.
- reconcilePositions returns the previous record when no item moved and no key was added or removed, so an identical relayout reuses the same object and hits reanimated's === dedup instead of waking every per-item derived. - Guard the grid overriddenCellDimensions write so an unchanged column width no longer wakes every cell's animated style on container resize. - Move reconcilePositions to utils/layout.ts; keep equality.ts to predicates. - activeItemPosition now only seeds the initial mutable; live active updates flow through the CommonValuesProvider dispatcher. - Return the runOnUI runner directly from the registration effect cleanup.
- Use the existing getKeyToIndex worklet in the flex insert swap indices instead of rebuilding the map with Object.fromEntries. - Memoize SortableFlex's processed children and wrap it in typedMemo, so it no longer re-renders every item on an unrelated parent render (parity with SortableGrid). - Drop the keyToGroup shared value; derive the active item's group by scanning the applied layout groups, removing an O(N) map rebuilt on every flex layout. - Skip the center-origin trigger position write when it is unchanged so a stationary finger stops waking the order-updater reaction.
MatiPl01
marked this pull request as ready for review
July 5, 2026 12:36
SortableFlex takes its items as `children` (inline JSX), so `children` is a new reference on every parent render. That makes useMemo(..., [children]) recompute each render and React.memo never skip, so neither guard actually helps the idiomatic usage. Unlike SortableGrid, which keys its memo off a stable `data` prop, there is nothing stable to memoize here.
- itemPositions -> itemLayoutPositions (the layout-computed slot each item animates toward) and itemPositionValues -> itemRenderPositions (the per-item mutables holding the currently rendered position), so the two are easy to tell apart. The layout-result `itemPositions` field is left as is. - Drop the verbose comments added alongside the earlier perf changes; the names now carry the intent.
"Current" reads clearer for a per-item mutable that holds where the item is actually drawn right now (the active item follows the finger; the others animate toward their itemLayoutPositions slot).
MatiPl01
added a commit
that referenced
this pull request
Jul 7, 2026
) Occasionally a dragged item would overlap a neighbour and, on drop, two items settled on the same slot with another slot left empty. The inactive-item layout reaction skipped re-issuing its `withTiming` when the new target was within a pixel of the item's *current, mid-animation* position. A rapid re-order that flipped a displaced neighbour's target back mid-flight was therefore swallowed while the item was transiently near that target, and it kept animating to the wrong slot. #586's `reconcilePositions` stopped handing out fresh position refs for unchanged items, which removed an accidental self-correction that had been hiding this, so it surfaced then. The skip now also requires the layout target itself to be unchanged since the previous update, read from the reaction's own `prev` value (the same signal the drop-interpolation branch already uses). It adds no state and keeps the original sub-pixel `position` check, so it is strictly a superset of the previous animation triggers and smooth-resize following is unchanged, while any real re-target now issues a fresh animation. Note: the original overlap is timing-dependent and I could not reproduce it with scripted gestures, so this rests on the code trace rather than a captured before/after. Verified with typecheck, eslint, all 21 library tests, and rapid cross-row plus over-and-back re-target drags on device (every item settles to a distinct slot, no regression).
MatiPl01
pushed a commit
that referenced
this pull request
Jul 23, 2026
# [1.10.0](v1.9.4...v1.10.0) (2026-07-23) ### Bug Fixes * Add item zIndex preserving based on the order of items ([#534](#534)) ([fb0be4e](fb0be4e)), closes [#36877](https://github.com/MatiPl01/react-native-sortables/issues/36877) * allow scrolling the ScrollView from draggable items on web ([#574](#574)) ([74879ed](74879ed)), closes [#545](#545) * Balance onTouchesUp on Sortable.Touchable long press ([#594](#594)) ([f977567](f977567)) * Deduplicate expo example native modules for expo-doctor ([#590](#590)) ([e5af169](e5af169)), closes [#589](#589) * disable dragging for fixed-order and non-draggable handles ([#598](#598)) ([8ed2e81](8ed2e81)) * dragging stops after a screen is detached and re-attached (gesture-handler v3) ([#573](#573)) ([c70a14f](c70a14f)), closes [#349](#349) [react-native-gesture-handler#3560](https://github.com/react-native-gesture-handler/issues/3560) [#349](#349) [#349](#349) * Item can settle on another item's slot after a rapid re-order ([#591](#591)) ([9d63817](9d63817)), closes [#586](#586) * Item position drift on slow web container resize ([#562](#562)) ([8973115](8973115)) * Keep npm version from walking yarn workspaces during release ([#609](#609)) ([0d8d869](0d8d869)) * Keep Sortable.Grid measurements when revealed after react-freeze ([#572](#572)) ([609c73c](609c73c)), closes [#519](#519) [#36877](https://github.com/MatiPl01/react-native-sortables/issues/36877) * keep Sortable.Touchable onTouchesUp firing on web ([#596](#596)) ([8529fa8](8529fa8)), closes [#594](#594) * Keep Sortable.Touchable tap and long press working on teleported items ([#602](#602)) ([3efb111](3efb111)) * Keep teleported item in sync with collapsed content ([#595](#595)) ([1fbcf9e](1fbcf9e)) * New-Arch grid mount crash in the example and unrunnable expo example ([#589](#589)) ([01aa1a8](01aa1a8)) * Respect Sortable.Flex alignItems before items are measured ([#560](#560)) ([5c84117](5c84117)), closes [#258](#258) * Restore tap and long press on teleported items ([#599](#599)) ([e73117b](e73117b)) * Sortable.Touchable crash on gesture-handler v3 (onActivate not a worklet) ([#587](#587)) ([4b3007e](4b3007e)) * Support react-native-gesture-handler 3.x types ([#553](#553)) ([83754e0](83754e0)) * Support react-native-reanimated 4.5 / worklets 0.10 ([#566](#566)) ([828c6d6](828c6d6)), closes [/github.com//issues/552#issuecomment-4634261543](https://github.com//github.com/MatiPl01/react-native-sortables/issues/552/issues/issuecomment-4634261543) [#552](#552) ### Features * Add react-native-pulsar as a top-priority haptics adapter ([#568](#568)) ([642e734](642e734)) * Auto-detect expo-haptics when available ([#558](#558)) ([a0d7602](a0d7602)), closes [#554](#554) * Reverse items stacking order ([#535](#535)) ([14c7d82](14c7d82)), closes [#36877](https://github.com/MatiPl01/react-native-sortables/issues/36877) ### Performance Improvements * Drive active item position from a single reaction ([#586](#586)) ([c498091](c498091)), closes [#584](#584)
Owner
Author
|
馃帀 This PR is included in version 1.10.0 馃帀 The release is available on: Your semantic-release bot 馃摝馃殌 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the drag-lag regression that appeared after the reanimated 4.5.1 / worklets 0.10.1 / RN 0.86 bump (#584). The library source was unchanged; the bump made a pre-existing O(N)-per-frame cost expensive.
During a drag,
activeItemPositionupdates every frame, and every item's layout reaction subscribed to it, so dragging one item did work proportional to the whole list each frame. Each item now registers its position mutable and a single reaction inCommonValuesProviderdrives only the active item's mutable, so inactive items do no per-frame work.Also trims related redundant work on the drag and layout path: reused position references for unchanged items, guarded no-op layout and cell-dimension writes, and lighter flex group bookkeeping.