Skip to content

Commit 90da8c3

Browse files
authored
Picker Refactor #1 (#1000)
* Migrate PickerItem from Class to a Function component * use useMeme for PickerItem selectedIndicator * pass missing dependency in PickerItem * Fix default selectedIconColor in PickerItem
1 parent 5680e2d commit 90da8c3

File tree

1 file changed

+115
-130
lines changed

1 file changed

+115
-130
lines changed

src/components/picker/PickerItem.js

Lines changed: 115 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import _ from 'lodash';
22
import PropTypes from 'prop-types';
3-
import React from 'react';
3+
import React, {useCallback, useEffect, useMemo} from 'react';
44
import {StyleSheet} from 'react-native';
55
import {Colors, Typography} from '../../style';
6-
import {BaseComponent} from '../../commons';
6+
import * as Modifiers from '../../commons/modifiers';
77
import Assets from '../../assets';
88
import View from '../view';
99
import TouchableOpacity from '../touchableOpacity';
@@ -16,153 +16,138 @@ import Text from '../text';
1616
* @extendslink: docs/TouchableOpacity
1717
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/PickerScreen.js
1818
*/
19-
class PickerItem extends BaseComponent {
20-
static displayName = 'Picker.Item';
21-
22-
static propTypes = {
23-
/**
24-
* Item's value
25-
*/
26-
value: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.number]),
27-
/**
28-
* Item's label
29-
*/
30-
label: PropTypes.string,
31-
/**
32-
* Custom function for the item label (e.g (value) => customLabel)
33-
*/
34-
getItemLabel: PropTypes.func,
35-
/**
36-
* DEPRECATE: Function to return the value out of the item value prop when value is custom shaped.
37-
*/
38-
getItemValue: PropTypes.func,
39-
/**
40-
* Is the item selected
41-
*/
42-
isSelected: PropTypes.bool,
43-
/**
44-
* Pass to change the selected icon
45-
*/
46-
selectedIcon: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
47-
/**
48-
* Pass to change the selected icon color
49-
*/
50-
selectedIconColor: PropTypes.string,
51-
/**
52-
* Is the item disabled
53-
*/
54-
disabled: PropTypes.bool,
55-
/**
56-
* Render custom item
57-
*/
58-
renderItem: PropTypes.elementType,
59-
/**
60-
* Callback for onPress action
61-
*/
62-
onPress: PropTypes.func,
63-
/**
64-
* Callback for onLayout event
65-
*/
66-
onSelectedLayout: PropTypes.func
67-
};
68-
69-
70-
constructor(props) {
71-
super(props);
72-
73-
if (_.isPlainObject(props.value)) {
19+
const PickerItem = props => {
20+
const {
21+
renderItem,
22+
value,
23+
label,
24+
onPress,
25+
disabled,
26+
isSelected,
27+
selectedIcon = Assets.icons.check,
28+
selectedIconColor = Colors.primary,
29+
testID
30+
} = props;
31+
32+
useEffect(() => {
33+
if (_.isPlainObject(value)) {
7434
console.warn('UILib Picker.Item will stop supporting passing object as value & label (e.g {value, label}) in the next major version. Please pass separate label and value props');
7535
}
76-
}
77-
36+
}, [value]);
7837

79-
generateStyles() {
80-
this.styles = createStyles(this.props);
81-
}
38+
// TODO: deprecate the check for object
39+
const _onPress = useCallback(() => {
40+
// onPress(_.isObject(value) ? value : {value, label});
41+
onPress(value);
42+
}, [value, onPress]);
8243

83-
getLabel() {
84-
const {value, label} = this.props;
44+
const onSelectedLayout = useCallback((...args) => {
45+
_.invoke(props, 'onSelectedLayout', ...args);
46+
}, []);
8547

48+
const getLabel = () => {
8649
if (_.isObject(value)) {
87-
return _.invoke(this.props, 'getItemLabel', value) || _.get(value, 'label');
50+
return _.invoke(props, 'getItemLabel', value) || _.get(value, 'label');
8851
}
8952
return label;
90-
}
91-
92-
renderSelectedIndicator() {
93-
const {
94-
isSelected,
95-
disabled,
96-
selectedIcon = Assets.icons.check,
97-
selectedIconColor = Colors.primary
98-
} = this.props;
53+
};
9954

55+
const selectedIndicator = useMemo(() => {
10056
if (isSelected) {
10157
return <Image source={selectedIcon} tintColor={disabled ? Colors.dark60 : selectedIconColor}/>;
10258
}
103-
}
104-
105-
renderItem() {
106-
const {disabled} = this.props;
59+
}, [isSelected, disabled, selectedIcon, selectedIconColor]);
10760

61+
const _renderItem = () => {
10862
return (
109-
<View style={this.styles.container} flex row spread centerV>
110-
<Text numberOfLines={1} style={[this.styles.labelText, disabled && this.styles.labelTextDisabled]}>
111-
{this.getLabel()}
63+
<View style={styles.container} flex row spread centerV>
64+
<Text numberOfLines={1} style={[styles.labelText, disabled && styles.labelTextDisabled]}>
65+
{getLabel()}
11266
</Text>
113-
{this.renderSelectedIndicator()}
67+
{selectedIndicator}
11468
</View>
11569
);
116-
}
117-
118-
onSelectedLayout = (...args) => {
119-
_.invoke(this.props, 'onSelectedLayout', ...args);
12070
};
12171

122-
render() {
123-
const {renderItem, value, disabled, isSelected, testID} = this.props;
124-
125-
return (
126-
<TouchableOpacity
127-
activeOpacity={0.5}
128-
onPress={this.onPress}
129-
onLayout={isSelected ? this.onSelectedLayout : undefined}
130-
disabled={disabled}
131-
testID={testID}
132-
throttleTime={0}
133-
{...this.extractAccessibilityProps()}
134-
>
135-
{renderItem ? renderItem(value, this.props, this.getLabel()) : this.renderItem()}
136-
</TouchableOpacity>
137-
);
72+
return (
73+
<TouchableOpacity
74+
activeOpacity={0.5}
75+
onPress={_onPress}
76+
onLayout={isSelected ? onSelectedLayout : undefined}
77+
disabled={disabled}
78+
testID={testID}
79+
throttleTime={0}
80+
{...Modifiers.extractAccessibilityProps(props)}
81+
>
82+
{renderItem ? renderItem(value, props, getLabel()) : _renderItem()}
83+
</TouchableOpacity>
84+
);
85+
};
86+
87+
const styles = StyleSheet.create({
88+
container: {
89+
height: 56.5,
90+
paddingHorizontal: 23,
91+
borderColor: Colors.rgba(Colors.dark10, 0.1),
92+
borderBottomWidth: 1
93+
},
94+
labelText: {
95+
...Typography.text70,
96+
color: Colors.dark10,
97+
flex: 1,
98+
textAlign: 'left'
99+
},
100+
labelTextDisabled: {
101+
color: Colors.dark60
138102
}
139-
140-
// TODO: deprecate the check for object
141-
onPress = () => {
142-
const {value, label, onPress} = this.props;
143-
onPress(_.isObject(value) ? value : {value, label});
144-
// onPress(value);
145-
};
146-
}
147-
148-
function createStyles() {
149-
return StyleSheet.create({
150-
container: {
151-
height: 56.5,
152-
paddingHorizontal: 23,
153-
borderColor: Colors.rgba(Colors.dark10, 0.1),
154-
borderBottomWidth: 1
155-
},
156-
labelText: {
157-
...Typography.text70,
158-
color: Colors.dark10,
159-
flex: 1,
160-
textAlign: 'left'
161-
},
162-
labelTextDisabled: {
163-
color: Colors.dark60
164-
}
165-
});
166-
}
103+
});
104+
105+
PickerItem.displayName = 'Picker.Item';
106+
PickerItem.propTypes = {
107+
/**
108+
* Item's value
109+
*/
110+
value: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.number]),
111+
/**
112+
* Item's label
113+
*/
114+
label: PropTypes.string,
115+
/**
116+
* Custom function for the item label (e.g (value) => customLabel)
117+
*/
118+
getItemLabel: PropTypes.func,
119+
/**
120+
* DEPRECATE: Function to return the value out of the item value prop when value is custom shaped.
121+
*/
122+
getItemValue: PropTypes.func,
123+
/**
124+
* Is the item selected
125+
*/
126+
isSelected: PropTypes.bool,
127+
/**
128+
* Pass to change the selected icon
129+
*/
130+
selectedIcon: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
131+
/**
132+
* Pass to change the selected icon color
133+
*/
134+
selectedIconColor: PropTypes.string,
135+
/**
136+
* Is the item disabled
137+
*/
138+
disabled: PropTypes.bool,
139+
/**
140+
* Render custom item
141+
*/
142+
renderItem: PropTypes.elementType,
143+
/**
144+
* Callback for onPress action
145+
*/
146+
onPress: PropTypes.func,
147+
/**
148+
* Callback for onLayout event
149+
*/
150+
onSelectedLayout: PropTypes.func
151+
};
167152

168153
export default PickerItem;

0 commit comments

Comments
 (0)