Skip to content

Commit e65796a

Browse files
authored
support onOpen for android (#277)
* support onOpen for android * fix test * fix test
1 parent ae5bed9 commit e65796a

File tree

5 files changed

+78
-62
lines changed

5 files changed

+78
-62
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
### 6.4.0
2+
3+
##### New
4+
5+
- Opened up `onOpen` prop to now support Android when in headless or `useNativeAndroidPickerStyle={false}` mode
6+
7+
---
8+
19
### 6.3.4
210

311
##### Bugfix

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ export const Dropdown = () => {
6363
| `pickerProps` | Additional props to pass to the Picker (some props are used in core functionality so use this carefully) | object |
6464
| `Icon` | Custom icon component to be rendered.<br>_More details in [styling](#styling)_ | Component |
6565
| `textInputProps` | Additional props to pass to the TextInput (some props are used in core functionality so use this carefully). This is iOS only unless `useNativeAndroidPickerStyle={false}`. | object |
66+
| `onOpen`<br> | Callback triggered right before the opening of the picker<br>_Not supported when useNativeAndroidPickerStyle={true}_ | function |
6667
| `useNativeAndroidPickerStyle`<br>_Android only_ | The component defaults to using the native Android Picker in its un-selected state. Setting this flag to `false` will mimic the default iOS presentation where a tappable TextInput is displayed.<br>_More details in [styling](#styling)_ | boolean |
6768
| `InputAccessoryView`<br>_iOS only_ | Replace the InputAcessoryView section (bar with tabbing arrown and Done button) of the opened picker with your own custom component. Can also return `null` here to hide completely. While this bar is typical on `select` elements on the web, the [interface guidelines](https://developer.apple.com/ios/human-interface-guidelines/controls/pickers/) does not include it. View the [snack](https://snack.expo.io/@lfkwtz/react-native-picker-select) to see examples on how this can be customized. | boolean |
6869
| `doneText`<br>_iOS only_ | "Done" default text on the modal. Can be overwritten here | string |
6970
| `onUpArrow / onDownArrow`<br>_iOS only_ | Presence enables the corresponding arrow<br>- Closes the picker<br>- Calls the callback provided | function |
7071
| `onDonePress`<br>_iOS only_ | Callback when the 'Done' button is pressed | function |
71-
| `onOpen / onClose`<br>_iOS only_ | Callback triggered right before the opening or closing of the picker | function |
72+
| `onClose`<br>_iOS only_ | Callback triggered right before the closing of the picker | function |
7273
| `modalProps`<br>_iOS only_ | Additional props to pass to the Modal (some props are used in core functionality so use this carefully) | object |
7374

7475
### Styling
@@ -95,7 +96,7 @@ All properties mentioned below must be nested under the `style` prop. Examples o
9596

9697
## Testing
9798

98-
This component has been tested on React Native v0.51 - v0.59
99+
This component has been tested on React Native v0.51 - v0.61
99100

100101
[![BrowserStack](https://i.imgur.com/cOdhMed.png)](https://www.browserstack.com/)
101102

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-picker-select",
3-
"version": "6.3.4",
3+
"version": "6.4.0",
44
"description": "A Picker component for React Native which emulates the native <select> interfaces for each platform",
55
"license": "MIT",
66
"author": "Michael Lefkowitz <[email protected]>",

src/index.js

+21-19
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default class RNPickerSelect extends PureComponent {
3838
style: PropTypes.shape({}),
3939
children: PropTypes.any, // eslint-disable-line react/forbid-prop-types
4040
placeholderTextColor: ColorPropType, // deprecated
41+
onOpen: PropTypes.func,
4142
useNativeAndroidPickerStyle: PropTypes.bool,
4243

4344
// Custom Modal props (iOS only)
@@ -46,7 +47,6 @@ export default class RNPickerSelect extends PureComponent {
4647
onDonePress: PropTypes.func,
4748
onUpArrow: PropTypes.func,
4849
onDownArrow: PropTypes.func,
49-
onOpen: PropTypes.func,
5050
onClose: PropTypes.func,
5151

5252
// Modal props (iOS only)
@@ -457,27 +457,29 @@ export default class RNPickerSelect extends PureComponent {
457457
}
458458

459459
renderAndroidHeadless() {
460-
const { disabled, Icon, style, pickerProps } = this.props;
460+
const { disabled, Icon, style, pickerProps, onOpen } = this.props;
461461
const { selectedItem } = this.state;
462462

463463
return (
464-
<View style={style.headlessAndroidContainer}>
465-
{this.renderTextInputOrChildren()}
466-
<Picker
467-
style={[
468-
Icon ? { backgroundColor: 'transparent' } : {}, // to hide native icon
469-
defaultStyles.headlessAndroidPicker,
470-
style.headlessAndroidPicker,
471-
]}
472-
testID="android_picker_headless"
473-
enabled={!disabled}
474-
onValueChange={this.onValueChange}
475-
selectedValue={selectedItem.value}
476-
{...pickerProps}
477-
>
478-
{this.renderPickerItems()}
479-
</Picker>
480-
</View>
464+
<TouchableWithoutFeedback onPress={onOpen} testID="android_touchable_wrapper">
465+
<View style={style.headlessAndroidContainer}>
466+
{this.renderTextInputOrChildren()}
467+
<Picker
468+
style={[
469+
Icon ? { backgroundColor: 'transparent' } : {}, // to hide native icon
470+
defaultStyles.headlessAndroidPicker,
471+
style.headlessAndroidPicker,
472+
]}
473+
testID="android_picker_headless"
474+
enabled={!disabled}
475+
onValueChange={this.onValueChange}
476+
selectedValue={selectedItem.value}
477+
{...pickerProps}
478+
>
479+
{this.renderPickerItems()}
480+
</Picker>
481+
</View>
482+
</TouchableWithoutFeedback>
481483
);
482484
}
483485

test/test.js

+45-40
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ const placeholder = {
3636
value: null,
3737
};
3838

39+
const noop = () => {};
40+
3941
describe('RNPickerSelect', () => {
4042
beforeEach(() => {
41-
// jest.useFakeTimers();
4243
jest.resetAllMocks();
4344
jest.spyOn(Keyboard, 'dismiss');
4445
});
@@ -64,7 +65,7 @@ describe('RNPickerSelect', () => {
6465
placeholder={placeholder}
6566
itemKey="usa"
6667
value={1}
67-
onValueChange={() => {}}
68+
onValueChange={noop}
6869
/>
6970
);
7071

@@ -74,11 +75,7 @@ describe('RNPickerSelect', () => {
7475

7576
it('should set the selected value to state', () => {
7677
const wrapper = shallow(
77-
<RNPickerSelect
78-
items={selectItems}
79-
placeholder={placeholder}
80-
onValueChange={() => {}}
81-
/>
78+
<RNPickerSelect items={selectItems} placeholder={placeholder} onValueChange={noop} />
8279
);
8380

8481
wrapper
@@ -97,7 +94,7 @@ describe('RNPickerSelect', () => {
9794
<RNPickerSelect
9895
items={selectItems}
9996
placeholder={placeholder}
100-
onValueChange={() => {}}
97+
onValueChange={noop}
10198
InputAccessoryView={() => {
10299
return <View />;
103100
}}
@@ -112,7 +109,7 @@ describe('RNPickerSelect', () => {
112109
});
113110

114111
it('should update the orientation state when onOrientationChange is called', () => {
115-
const wrapper = shallow(<RNPickerSelect items={[]} onValueChange={() => {}} />);
112+
const wrapper = shallow(<RNPickerSelect items={[]} onValueChange={noop} />);
116113

117114
expect(wrapper.state().orientation).toEqual('portrait');
118115

@@ -123,7 +120,7 @@ describe('RNPickerSelect', () => {
123120

124121
it('should handle an empty items array', () => {
125122
const wrapper = shallow(
126-
<RNPickerSelect items={[]} placeholder={{}} onValueChange={() => {}} />
123+
<RNPickerSelect items={[]} placeholder={{}} onValueChange={noop} />
127124
);
128125

129126
expect(wrapper.state().items).toHaveLength(0);
@@ -148,11 +145,7 @@ describe('RNPickerSelect', () => {
148145

149146
it('should show the picker when pressed', () => {
150147
const wrapper = shallow(
151-
<RNPickerSelect
152-
items={selectItems}
153-
placeholder={placeholder}
154-
onValueChange={() => {}}
155-
/>
148+
<RNPickerSelect items={selectItems} placeholder={placeholder} onValueChange={noop} />
156149
);
157150

158151
const touchable = wrapper.find('TouchableWithoutFeedback').at(1);
@@ -165,7 +158,7 @@ describe('RNPickerSelect', () => {
165158
<RNPickerSelect
166159
items={selectItems}
167160
placeholder={placeholder}
168-
onValueChange={() => {}}
161+
onValueChange={noop}
169162
disabled
170163
/>
171164
);
@@ -199,11 +192,7 @@ describe('RNPickerSelect', () => {
199192

200193
it('should update the items when the `items` prop updates', () => {
201194
const wrapper = shallow(
202-
<RNPickerSelect
203-
items={selectItems}
204-
placeholder={placeholder}
205-
onValueChange={() => {}}
206-
/>
195+
<RNPickerSelect items={selectItems} placeholder={placeholder} onValueChange={noop} />
207196
);
208197

209198
expect(wrapper.state().items).toEqual([placeholder].concat(selectItems));
@@ -216,7 +205,7 @@ describe('RNPickerSelect', () => {
216205

217206
it('should should handle having no placeholder', () => {
218207
const wrapper = shallow(
219-
<RNPickerSelect items={selectItems} placeholder={{}} onValueChange={() => {}} />
208+
<RNPickerSelect items={selectItems} placeholder={{}} onValueChange={noop} />
220209
);
221210

222211
expect(wrapper.state().items).toEqual(selectItems);
@@ -226,7 +215,7 @@ describe('RNPickerSelect', () => {
226215
const wrapper = shallow(
227216
<RNPickerSelect
228217
items={selectItems}
229-
onValueChange={() => {}}
218+
onValueChange={noop}
230219
Icon={() => {
231220
return <View />;
232221
}}
@@ -237,13 +226,13 @@ describe('RNPickerSelect', () => {
237226
});
238227

239228
it('should should not show the icon container when the Icon prop is empty', () => {
240-
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={() => {}} />);
229+
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={noop} />);
241230

242231
expect(wrapper.find('[testID="icon_container"]')).toHaveLength(0);
243232
});
244233

245234
it('should call Keyboard.dismiss when opened', () => {
246-
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={() => {}} />);
235+
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={noop} />);
247236

248237
const touchable = wrapper.find('[testID="ios_touchable_wrapper"]');
249238
touchable.simulate('press');
@@ -256,7 +245,7 @@ describe('RNPickerSelect', () => {
256245
<RNPickerSelect
257246
items={selectItems}
258247
placeholder={placeholder}
259-
onValueChange={() => {}}
248+
onValueChange={noop}
260249
value={undefined}
261250
/>
262251
);
@@ -272,7 +261,7 @@ describe('RNPickerSelect', () => {
272261

273262
it('should set the selected value to state (Android)', () => {
274263
Platform.OS = 'android';
275-
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={() => {}} />);
264+
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={noop} />);
276265

277266
wrapper
278267
.find('[testID="android_picker"]')
@@ -284,7 +273,7 @@ describe('RNPickerSelect', () => {
284273
it('should render the headless component when a child is passed in (Android)', () => {
285274
Platform.OS = 'android';
286275
const wrapper = shallow(
287-
<RNPickerSelect items={selectItems} onValueChange={() => {}}>
276+
<RNPickerSelect items={selectItems} onValueChange={noop}>
288277
<View />
289278
</RNPickerSelect>
290279
);
@@ -297,11 +286,7 @@ describe('RNPickerSelect', () => {
297286
Platform.OS = 'ios';
298287
const onDonePressSpy = jest.fn();
299288
const wrapper = shallow(
300-
<RNPickerSelect
301-
items={selectItems}
302-
onValueChange={() => {}}
303-
onDonePress={onDonePressSpy}
304-
/>
289+
<RNPickerSelect items={selectItems} onValueChange={noop} onDonePress={onDonePressSpy} />
305290
);
306291

307292
wrapper.find('[testID="done_button"]').simulate('press');
@@ -315,7 +300,7 @@ describe('RNPickerSelect', () => {
315300
const wrapper = shallow(
316301
<RNPickerSelect
317302
items={selectItems}
318-
onValueChange={() => {}}
303+
onValueChange={noop}
319304
modalProps={{
320305
onShow: onShowSpy,
321306
}}
@@ -334,7 +319,7 @@ describe('RNPickerSelect', () => {
334319
const wrapper = shallow(
335320
<RNPickerSelect
336321
items={selectItems}
337-
onValueChange={() => {}}
322+
onValueChange={noop}
338323
modalProps={{
339324
onDismiss: onDismissSpy,
340325
}}
@@ -347,22 +332,42 @@ describe('RNPickerSelect', () => {
347332
expect(onDismissSpy).toHaveBeenCalledWith();
348333
});
349334

350-
it('should call the onOpen callback when set', () => {
335+
it('should call the onOpen callback when set (iOS)', () => {
336+
Platform.OS = 'ios';
351337
const onOpenSpy = jest.fn();
352338
const wrapper = shallow(
353-
<RNPickerSelect items={selectItems} onValueChange={() => {}} onOpen={onOpenSpy} />
339+
<RNPickerSelect items={selectItems} onValueChange={noop} onOpen={onOpenSpy} />
354340
);
355341

356-
const touchable = wrapper.find('[testID="done_button"]');
342+
const touchable = wrapper.find('[testID="ios_touchable_wrapper"]');
343+
touchable.simulate('press');
344+
345+
expect(onOpenSpy).toHaveBeenCalledWith();
346+
});
347+
348+
it('should call the onOpen callback when set (Android)', () => {
349+
Platform.OS = 'android';
350+
const onOpenSpy = jest.fn();
351+
const wrapper = shallow(
352+
<RNPickerSelect
353+
items={selectItems}
354+
onValueChange={noop}
355+
onOpen={onOpenSpy}
356+
useNativeAndroidPickerStyle={false}
357+
/>
358+
);
359+
360+
const touchable = wrapper.find('[testID="android_touchable_wrapper"]');
357361
touchable.simulate('press');
358362

359363
expect(onOpenSpy).toHaveBeenCalledWith();
360364
});
361365

362366
it('should call the onClose callback when set', () => {
367+
Platform.OS = 'ios';
363368
const onCloseSpy = jest.fn();
364369
const wrapper = shallow(
365-
<RNPickerSelect items={selectItems} onValueChange={() => {}} onClose={onCloseSpy} />
370+
<RNPickerSelect items={selectItems} onValueChange={noop} onClose={onCloseSpy} />
366371
);
367372

368373
const touchable = wrapper.find('[testID="done_button"]');
@@ -375,7 +380,7 @@ describe('RNPickerSelect', () => {
375380
});
376381

377382
it('should close the modal when the empty area above the picker is tapped', () => {
378-
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={() => {}} />);
383+
const wrapper = shallow(<RNPickerSelect items={selectItems} onValueChange={noop} />);
379384

380385
jest.spyOn(wrapper.instance(), 'togglePicker');
381386

0 commit comments

Comments
 (0)