fix(ios): discard stale deferred picker selections - #675
Open
lptn wants to merge 1 commit into
Open
Conversation
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.
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, nativeonValueChange, accessibility label, test IDs, and a visible selected value. It intentionally uses no timers or JavaScript workarounds.Reproduction and root cause
<PickerIOS> with rapid controlled updates.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_selectedIndexand dispatchonChange, after which React may commit the newer controlled value. The old block previously still unconditionally calledselectRow:. AreloadAllComponentsbetween 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
selectionGenerationfor each distinct controlled selection, item replacement, reload, and nativedidSelectRow:callback (both legacy and Fabric paths).selectRow:apply only when its captured generation is still current.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 queueddispatch_asyncselections 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-installinexample/✅clang-format --dry-run --Werror ios/RNCPicker.mmcould not run becauseclang-formatis not installed in the environment.TypeError: Cannot read properties of undefined (reading 'testEnvironmentOptions')).react-nativebecause the example has no React Native Jest preset.No Objective-C unit-test target exists in this repository. The example provides the exact manual regression path for both old and new architectures.