Skip to content

Commit 50eb031

Browse files
authored
Infra/picker refactor #2 (#1001)
* Migrate PickerItem from Class to a Function component * Use context to communicate Picker current value with Picker Items * Fix issue with multi select mode when using the new Picker API * use useMeme for PickerItem selectedIndicator * pass missing dependency in PickerItem * Fix default selectedIconColor in PickerItem * Fix lint issue * Fix order of things in PickerItem * Use LogService for deprecation warnings
1 parent dbeda13 commit 50eb031

File tree

6 files changed

+108
-91
lines changed

6 files changed

+108
-91
lines changed

demo/src/screens/componentScreens/PickerScreen.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default class PickerScreen extends Component {
2828
itemsCount: 1,
2929
// language: {value: 'java', label: 'Java'},
3030
language: undefined,
31-
language2: undefined, // for migrated picker example
31+
language2: options[2].value, // for migrated picker example
3232
languages: [],
3333
nativePickerValue: 'java',
3434
customModalValues: [],
@@ -243,6 +243,7 @@ export default class PickerScreen extends Component {
243243
showSearch
244244
searchPlaceholder={'Search a language'}
245245
searchStyle={{color: Colors.blue30, placeholderTextColor: Colors.dark50}}
246+
// mode={Picker.modes.MULTI}
246247
// useNativePicker
247248
>
248249
{_.map(options, option => (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import React from 'react';
2+
3+
export default React.createContext({});

src/components/picker/PickerItem.js

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import _ from 'lodash';
22
import PropTypes from 'prop-types';
3-
import React, {useCallback, useEffect, useMemo} from 'react';
3+
import React, {useCallback, useEffect, useMemo, useContext} from 'react';
44
import {StyleSheet} from 'react-native';
5+
import {LogService} from '../../services';
56
import {Colors, Typography} from '../../style';
67
import * as Modifiers from '../../commons/modifiers';
78
import Assets from '../../assets';
89
import View from '../view';
910
import TouchableOpacity from '../touchableOpacity';
1011
import Image from '../image';
1112
import Text from '../text';
13+
import {getItemLabel, isItemSelected, shouldFilterOut} from './PickerPresenter';
14+
import PickerContext from './PickerContext';
1215

1316
/**
1417
* @description: Picker.Item, for configuring the Picker's selectable options
@@ -18,57 +21,58 @@ import Text from '../text';
1821
*/
1922
const PickerItem = props => {
2023
const {
21-
renderItem,
2224
value,
2325
label,
24-
onPress,
2526
disabled,
26-
isSelected,
2727
selectedIcon = Assets.icons.check,
2828
selectedIconColor = Colors.primary,
2929
testID
3030
} = props;
31+
const context = useContext(PickerContext);
32+
const {renderItem} = context;
33+
const isSelected = isItemSelected(value, context.value);
34+
const itemLabel = getItemLabel(label, value, props.getItemLabel || context.getItemLabel);
35+
const accessibilityProps = {
36+
accessibilityState: isSelected ? {selected: true} : undefined,
37+
accessibilityHint: 'Double click to select this suggestion',
38+
...Modifiers.extractAccessibilityProps(props)
39+
};
3140

3241
useEffect(() => {
3342
if (_.isPlainObject(value)) {
34-
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');
43+
LogService.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');
3544
}
3645
}, [value]);
3746

38-
// TODO: deprecate the check for object
39-
const _onPress = useCallback(() => {
40-
// onPress(_.isObject(value) ? value : {value, label});
41-
onPress(value);
42-
}, [value, onPress]);
43-
44-
const onSelectedLayout = useCallback((...args) => {
45-
_.invoke(props, 'onSelectedLayout', ...args);
46-
}, []);
47-
48-
const getLabel = () => {
49-
if (_.isObject(value)) {
50-
return _.invoke(props, 'getItemLabel', value) || _.get(value, 'label');
51-
}
52-
return label;
53-
};
54-
5547
const selectedIndicator = useMemo(() => {
5648
if (isSelected) {
5749
return <Image source={selectedIcon} tintColor={disabled ? Colors.dark60 : selectedIconColor}/>;
5850
}
5951
}, [isSelected, disabled, selectedIcon, selectedIconColor]);
6052

53+
const _onPress = useCallback(() => {
54+
context.onPress(value);
55+
}, [value, context.onPress]);
56+
57+
const onSelectedLayout = useCallback((...args) => {
58+
_.invoke(context, 'onSelectedLayout', ...args);
59+
}, []);
60+
6161
const _renderItem = () => {
6262
return (
6363
<View style={styles.container} flex row spread centerV>
6464
<Text numberOfLines={1} style={[styles.labelText, disabled && styles.labelTextDisabled]}>
65-
{getLabel()}
65+
{itemLabel}
6666
</Text>
6767
{selectedIndicator}
6868
</View>
6969
);
7070
};
7171

72+
if (context.showSearch && shouldFilterOut(context.searchValue, itemLabel)) {
73+
return null;
74+
}
75+
7276
return (
7377
<TouchableOpacity
7478
activeOpacity={0.5}
@@ -77,9 +81,9 @@ const PickerItem = props => {
7781
disabled={disabled}
7882
testID={testID}
7983
throttleTime={0}
80-
{...Modifiers.extractAccessibilityProps(props)}
84+
{...accessibilityProps}
8185
>
82-
{renderItem ? renderItem(value, props, getLabel()) : _renderItem()}
86+
{renderItem ? renderItem(value, props, itemLabel) : _renderItem()}
8387
</TouchableOpacity>
8488
);
8589
};

src/components/picker/PickerPresenter.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@ export function isItemSelected(childValue, selectedValue) {
1313

1414
export function getItemValue(props) {
1515
if (_.isArray(props.value)) {
16-
return props.getItemValue ?
17-
_.map(props.value, item => props.getItemValue(item)) :
18-
_.map(props.value, 'value');
16+
return props.getItemValue ? _.map(props.value, item => props.getItemValue(item)) : _.map(props.value, 'value');
1917
} else if (!_.isObject(props.value)) {
2018
return props.value;
2119
}
2220
return _.invoke(props, 'getItemValue', props.value) || _.get(props.value, 'value');
2321
}
2422

25-
export function getItemLabel(props) {
26-
return _.invoke(props, 'getLabel', props.value) || _.get(props.value, 'label') || _.get(props, 'label');
23+
export function getItemLabel(label, value, getItemLabel) {
24+
if (_.isObject(value)) {
25+
if (getItemLabel) {
26+
return getItemLabel(value);
27+
}
28+
return _.get(value, 'label');
29+
}
30+
return label;
31+
}
32+
33+
export function shouldFilterOut(searchValue, itemLabel) {
34+
return !_.includes(_.lowerCase(itemLabel), _.lowerCase(searchValue));
2735
}

src/components/picker/__tests__/PickerPresenter.spec.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,19 @@ describe('components/PickerPresenter', () => {
4040
});
4141

4242
describe('getItemLabel', () => {
43+
it('should return item label when value is not an object', () => {
44+
expect(uut.getItemLabel('label', 'value', undefined)).toEqual('label');
45+
});
46+
4347
it('should return item label when value is an object', () => {
4448
const itemProps = {value: {value: 'value', label: 'label'}};
45-
expect(uut.getItemLabel(itemProps)).toEqual('label');
49+
expect(uut.getItemLabel(undefined, itemProps.value, undefined)).toEqual('label');
4650
});
4751

4852
it('should return item label according to getLabel function ', () => {
4953
const getLabel = itemValue => `${itemValue.value} - ${itemValue.label}`;
5054
const itemProps = {value: {value: 'value', label: 'label'}, getLabel};
51-
expect(uut.getItemLabel(itemProps)).toEqual('value - label');
55+
expect(uut.getItemLabel(undefined, itemProps.value, getLabel)).toEqual('value - label');
5256
});
5357
});
5458
});

0 commit comments

Comments
 (0)