react-native/Libraries/Picker/PickerIOS.ios.js
Matt Revell c1849e7f4b Fix an issue with unsafe picker access to setNativeProps.
Summary:
Fixing an issue where PickerIOS and DatePicker are being accessed unsafely, As a side effect we are also using ref callbacks as oppose to strings.

Fixed after spotting an issue in our app where the picker is closed and the callback attempts to update native props for an item that no longer exists.
Closes https://github.com/facebook/react-native/pull/3920

Reviewed By: svcscm

Differential Revision: D2663634

Pulled By: nicklockwood

fb-gh-sync-id: 813b32a038f59864401d5d3985c7ea32f5e13301
2015-12-04 07:41:30 -08:00

120 lines
3.4 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule PickerIOS
*
* This is a controlled component version of RCTPickerIOS
*/
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var ReactChildren = require('ReactChildren');
var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants;
var StyleSheet = require('StyleSheet');
var View = require('View');
var requireNativeComponent = require('requireNativeComponent');
var PickerIOS = React.createClass({
mixins: [NativeMethodsMixin],
propTypes: {
...View.propTypes,
onValueChange: React.PropTypes.func,
selectedValue: React.PropTypes.any, // string or integer basically
},
getInitialState: function() {
return this._stateFromProps(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.setState(this._stateFromProps(nextProps));
},
// Translate PickerIOS prop and children into stuff that RCTPickerIOS understands.
_stateFromProps: function(props) {
var selectedIndex = 0;
var items = [];
ReactChildren.forEach(props.children, function (child, index) {
if (child.props.value === props.selectedValue) {
selectedIndex = index;
}
items.push({value: child.props.value, label: child.props.label});
});
return {selectedIndex, items};
},
render: function() {
return (
<View style={this.props.style}>
<RCTPickerIOS
ref={ picker => this._picker = picker }
style={styles.pickerIOS}
items={this.state.items}
selectedIndex={this.state.selectedIndex}
onChange={this._onChange}
/>
</View>
);
},
_onChange: function(event) {
if (this.props.onChange) {
this.props.onChange(event);
}
if (this.props.onValueChange) {
this.props.onValueChange(event.nativeEvent.newValue);
}
// The picker is a controlled component. This means we expect the
// on*Change handlers to be in charge of updating our
// `selectedValue` prop. That way they can also
// disallow/undo/mutate the selection of certain values. In other
// words, the embedder of this component should be the source of
// truth, not the native component.
if (this._picker && this.state.selectedIndex !== event.nativeEvent.newIndex) {
this._picker.setNativeProps({
selectedIndex: this.state.selectedIndex
});
}
},
});
PickerIOS.Item = React.createClass({
propTypes: {
value: React.PropTypes.any, // string or integer basically
label: React.PropTypes.string,
},
render: function() {
// These items don't get rendered directly.
return null;
},
});
var styles = StyleSheet.create({
pickerIOS: {
// The picker will conform to whatever width is given, but we do
// have to set the component's height explicitly on the
// surrounding view to ensure it gets rendered.
height: RCTPickerIOSConsts.ComponentHeight,
},
});
var RCTPickerIOS = requireNativeComponent('RCTPicker', PickerIOS, {
nativeOnly: {
items: true,
onChange: true,
selectedIndex: true,
},
});
module.exports = PickerIOS;