Skip to content

fix(ios): discard stale deferred picker selections - #675

Open
lptn wants to merge 1 commit into
react-native-picker:masterfrom
lptn:fix/ios-stale-deferred-selection
Open

fix(ios): discard stale deferred picker selections#675
lptn wants to merge 1 commit into
react-native-picker:masterfrom
lptn:fix/ios-stale-deferred-selection

Conversation

@lptn

@lptn lptn commented Sep 9, 2026

Copy link
Copy Markdown

Summary

Fix the iOS controlled picker so an obsolete deferred selectRow:animated: cannot run after a newer native selection, item update, or component reload.

The example now includes an iOS-only controlled-picker reproduction with first/last buttons, controlled selectedValue, native onValueChange, accessibility label, test IDs, and a visible selected value. It intentionally uses no timers or JavaScript workarounds.

Reproduction and root cause

  1. Run the iOS example and open <PickerIOS> with rapid controlled updates.
  2. Flick the wheel, then rapidly alternate Select first and Select last while it is moving.
  3. On an unfixed build, the queue ordering permits an earlier programmatic selection to replay after a later native/controlled selection.
  4. With this change, only the action matching the current native selection generation may call selectRow:.

The ordering is deterministic in the native implementation: -setSelectedIndex: captures a row and defers it on the main queue. Before its block runs, -pickerView:didSelectRow: may update _selectedIndex and dispatch onChange, after which React may commit the newer controlled value. The old block previously still unconditionally called selectRow:. A reloadAllComponents between the prop update and that block has the same problem.

A full simulator visual pass was not completed because the environment build was stopped while compiling React-Fabric dependencies; the exact manual reproduction remains in the example for reviewer verification.

Fix

  • Increment a native selectionGeneration for each distinct controlled selection, item replacement, reload, and native didSelectRow: callback (both legacy and Fabric paths).
  • Make deferred selectRow: apply only when its captured generation is still current.
  • After a real reload, queue the current valid row non-animated under the new generation, preserving controlled selection without replaying an older animation.
  • Avoid redundant number-of-lines/font reloads so an unchanged prop commit does not suppress a legitimate latest-selection animation.

Initial selection remains non-animated. Native input still updates the selected index and event callback; a controlled parent that rejects a native choice subsequently schedules its newer reset. Accessibility properties are untouched.

Relation to #674

This is intentionally separate from #674. #674 guards setSelectedIndex: during active scrolling to avoid a Fabric crash. It neither invalidates already queued dispatch_async selections nor preserves a controlled programmatic update skipped during a scroll. This change addresses the deferred callback ordering itself and preserves the newest controlled value.

Verification

  • npm exec -- prettier --check example/src/PickerIOSExample.tsx
  • npm run validate:eslint
  • git diff --check
  • npm exec -- pod-install in example/
  • clang-format --dry-run --Werror ios/RNCPicker.mm could not run because clang-format is not installed in the environment.
  • The targeted Jest check cannot initialize on the unmodified checkout: root Jest 26 loads React Native 0.76's Jest 29 environment (TypeError: Cannot read properties of undefined (reading 'testEnvironmentOptions')).
  • The example Jest check cannot transform react-native because the example has no React Native Jest preset.
  • An iOS Simulator build was started after successful pod install, but the environment validation window ended while React-Fabric dependencies were compiling; it was stopped before app launch. No simulator visual pass is claimed.

No Objective-C unit-test target exists in this repository. The example provides the exact manual regression path for both old and new architectures.

@lptn
lptn requested a review from Naturalclar as a code owner September 9, 2026 11:14
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.

1 participant