Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/getting-started/v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,26 @@ Removed (use `PanView` instead)
Use the `backgroundColor` prop instead of `contentContainerStyle={{backgroundColor}}`
Fix card being transparent on Android
`onCollapseChanged` will now be called after the animation has ended (as was intended)

### Picker
The component was refactored to simplify its API and improve type safety.

Props migration:
- `migrate` - Removed (new API is default)
- `getItemValue` - Removed (value extraction is now automatic from `item.value`)
- `getItemLabel` - Removed (label extraction is now automatic from `item.label`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: "Use item.<X> to get <X>"

- `renderPicker` → `renderInput`
- `renderCustomModal` → `renderOverlay`
- `renderCustomDialogHeader` → `renderHeader`
- `pickerModalProps` → `customPickerProps.modalProps`
- `onShow` → `customPickerProps.modalProps.onShow`
- `children` → `items` (recommended)

Additional notes:
- The picker now only supports primitive values (string | number) instead of object-based values
- For individual items, use `getItemLabel` prop on `Picker.Item` instead of the removed picker-level `getItemLabel`
- Items structure remains the same: `{label: string, value: primitive}` format is unchanged
- The `getLabel` prop still works for custom label formatting of the selected value
- Better TypeScript support and improved performance

Check out the full API: https://wix.github.io/react-native-ui-lib/docs/components/form/Picker
3 changes: 1 addition & 2 deletions src/components/picker/PickerItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ const PickerItem = (props: PickerItemProps) => {
} = props;
const context = useContext(PickerContext);
const customRenderItem = props.renderItem || context.renderItem;

const isSelected = isItemSelected(value, context.value);
const itemLabel = getItemLabel(label, value, props.getItemLabel || context.getItemLabel);
const itemLabel = getItemLabel(label, value, props.getItemLabel);
const selectedCounter = context.selectionLimit && _.isArray(context.value) && context.value?.length;
const accessibilityProps = {
accessibilityState: isSelected ? {selected: true} : undefined,
Expand Down
12 changes: 5 additions & 7 deletions src/components/picker/PickerPresenter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from 'lodash';
import React from 'react';
import {PickerProps, PickerSingleValue, PickerValue} from './types';
import {PickerProps, PickerSingleValue, PickerValue, PickerItemProps} from './types';

export function extractPickerItems(props: PickerProps) {
const {children} = props;
Expand Down Expand Up @@ -36,12 +36,10 @@ export function isItemSelected(childValue: PickerSingleValue, selectedValue?: Pi
// return _.invoke(props, 'getItemValue', props.value) || _.get(props.value, 'value');
// }

export function getItemLabel(label: string, value: PickerValue, getItemLabel?: PickerProps['getItemLabel']) {
if (_.isObject(value)) {
if (getItemLabel) {
return getItemLabel(value);
}
return _.get(value, 'label');
export function getItemLabel(label: string, value: PickerValue, getItemLabel?: PickerItemProps['getItemLabel']) {
if (getItemLabel) {
const customLabel = getItemLabel(value);
return customLabel !== undefined ? customLabel : label;
}
return label;
}
Expand Down
17 changes: 8 additions & 9 deletions src/components/picker/__tests__/PickerPresenter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,18 @@ describe('components/PickerPresenter', () => {
// });

describe('getItemLabel', () => {
it('should return item label when value is not an object', () => {
expect(uut.getItemLabel('label', 'value', undefined)).toEqual('label');
it('should return item label when no custom getItemLabel function is provided', () => {
expect(uut.getItemLabel('JavaScript', 'js', undefined)).toEqual('JavaScript');
});

it('should return item label when value is an object', () => {
const itemProps = {value: {value: 'value', label: 'label'}};
expect(uut.getItemLabel(undefined, itemProps.value, undefined)).toEqual('label');
it('should return custom label when getItemLabel function is provided', () => {
const customGetItemLabel = (value) => `Custom: ${value}`;
expect(uut.getItemLabel('JavaScript', 'js', customGetItemLabel)).toEqual('Custom: js');
});

it('should return item label according to getLabel function ', () => {
const getLabel = itemValue => `${itemValue.value} - ${itemValue.label}`;
const itemProps = {value: {value: 'value', label: 'label'}, getLabel};
expect(uut.getItemLabel(undefined, itemProps.value, getLabel)).toEqual('value - label');
it('should return original label when getItemLabel function returns undefined', () => {
const customGetItemLabel = () => undefined;
expect(uut.getItemLabel('JavaScript', 'js', customGetItemLabel)).toEqual('JavaScript');
});
});
});
8 changes: 4 additions & 4 deletions src/components/picker/helpers/usePickerLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import {PickerProps, PickerValue} from '../types';
interface UsePickerLabelProps
extends Pick<
PickerProps,
'value' | 'getLabel' | 'getItemLabel' | 'placeholder' | 'accessibilityLabel' | 'accessibilityHint'
'value' | 'getLabel' | 'placeholder' | 'accessibilityLabel' | 'accessibilityHint'
> {
items: {value: string | number; label: string}[] | null | undefined;
}

const usePickerLabel = (props: UsePickerLabelProps) => {
const {value, items, getLabel, getItemLabel, placeholder, accessibilityLabel, accessibilityHint} = props;
const {value, items, getLabel, placeholder, accessibilityLabel, accessibilityHint} = props;

const getLabelsFromArray = useCallback((value: PickerValue) => {
const itemsByValue = _.keyBy(items, 'value');
return _.flow(arr =>
_.map(arr, item => (_.isPlainObject(item) ? getItemLabel?.(item) || item?.label : itemsByValue[item]?.label)),
_.map(arr, item => (_.isPlainObject(item) ? item?.label : itemsByValue[item]?.label)),
arr => _.join(arr, ', '))(value);
}, [getItemLabel, items]);
}, [items]);

const _getLabel = useCallback((value: PickerValue) => {
if (_.isFunction(getLabel) && !_.isUndefined(getLabel(value))) {
Expand Down
12 changes: 2 additions & 10 deletions src/components/picker/helpers/usePickerMigrationWarnings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,15 @@ import {LogService} from '../../../services';
import {PickerProps} from '../types';

// TODO: Remove this whole file when migration is completed
type UsePickerMigrationWarnings = Pick<PickerProps, 'children' | 'getItemLabel' | 'getItemValue' | 'onShow'>;
type UsePickerMigrationWarnings = Pick<PickerProps, 'children' | 'onShow'>;

const usePickerMigrationWarnings = (props: UsePickerMigrationWarnings) => {
const {children, getItemLabel, getItemValue, onShow} = props;
const {children, onShow} = props;
useEffect(() => {
if (children) {
LogService.warn(`UILib Picker will stop supporting the 'children' prop in the next major version, please pass 'items' prop instead`);
}

if (getItemLabel) {
LogService.warn(`UILib Picker will stop supporting the 'getItemLabel' prop in the next major version, please pass the 'getItemLabel' prop to the specific item instead`);
}

if (getItemValue) {
LogService.warn(`UILib Picker will stop supporting the 'getItemValue' prop in the next major version, please stop using it. The value will be extract from 'items' prop instead`);
}

if (onShow) {
LogService.warn(`UILib Picker will stop supporting the 'onShow' prop in the next major version, please pass the 'onShow' prop from the 'pickerModalProps' instead`);
}
Expand Down
10 changes: 5 additions & 5 deletions src/components/picker/helpers/usePickerSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import _ from 'lodash';
import {PickerProps} from '../types';
import {getItemLabel as getItemLabelPresenter, shouldFilterOut} from '../PickerPresenter';

type UsePickerSearchProps = Pick<PickerProps, 'showSearch' | 'onSearchChange' | 'children' | 'getItemLabel' | 'items'>;
type UsePickerSearchProps = Pick<PickerProps, 'showSearch' | 'onSearchChange' | 'children' | 'items'>;

const usePickerSearch = (props: UsePickerSearchProps) => {
const {showSearch, onSearchChange, children, getItemLabel, items} = props;
const {showSearch, onSearchChange, children, items} = props;
const [searchValue, setSearchValue] = useState('');

const filterItems = useCallback((items: any) => {
if (showSearch && !_.isEmpty(searchValue)) {
return _.filter(items, item => {
const {label, value, getItemLabel: childGetItemLabel} = item.props || item;
const itemLabel = getItemLabelPresenter(label, value, childGetItemLabel || getItemLabel);
const {label, value, getItemLabel} = item.props || item;
const itemLabel = getItemLabelPresenter(label, value, getItemLabel);
return !shouldFilterOut(searchValue, itemLabel);
});
}
return items;
},
[showSearch, searchValue, getItemLabel]);
[showSearch, searchValue]);

const filteredItems = useMemo(() => {
return filterItems(children || items);
Expand Down
6 changes: 3 additions & 3 deletions src/components/picker/helpers/usePickerSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import _ from 'lodash';
import {PickerProps, PickerValue, PickerSingleValue, PickerMultiValue, PickerModes} from '../types';

interface UsePickerSelectionProps
extends Pick<PickerProps, 'value' | 'onChange' | 'getItemValue' | 'topBarProps' | 'mode' | 'items'> {
extends Pick<PickerProps, 'value' | 'onChange' | 'topBarProps' | 'mode' | 'items'> {
pickerExpandableRef: RefObject<any>;
setSearchValue: (searchValue: string) => void;
}

const usePickerSelection = (props: UsePickerSelectionProps) => {
const {value, onChange, topBarProps, pickerExpandableRef, getItemValue, setSearchValue, mode, items} = props;
const {value, onChange, topBarProps, pickerExpandableRef, setSearchValue, mode, items} = props;
const [multiDraftValue, setMultiDraftValue] = useState(value as PickerMultiValue);
const [multiFinalValue, setMultiFinalValue] = useState(value as PickerMultiValue);

Expand All @@ -33,7 +33,7 @@ const usePickerSelection = (props: UsePickerSelectionProps) => {
const newValue = _.xor(multiDraftValue, itemAsArray);
setMultiDraftValue(newValue);
},
[multiDraftValue, getItemValue]);
[multiDraftValue]);

const cancelSelect = useCallback(() => {
setSearchValue('');
Expand Down
13 changes: 1 addition & 12 deletions src/components/picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
listProps,
value,
getLabel,
getItemLabel,
getItemValue,
renderItem,
children,
useSafeArea,
Expand All @@ -88,9 +86,6 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
const pickerExpandable = useRef<ExpandableOverlayMethods>(null);
const pickerRef = useImperativePickerHandle(ref, pickerExpandable);

// TODO: Remove this when migration is completed, starting of v8
// usePickerMigrationWarnings({children, getItemLabel, getItemValue});

useEffect(() => {
if (propItems) {
setItems(propItems);
Expand All @@ -101,7 +96,7 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
filteredItems,
setSearchValue,
onSearchChange: _onSearchChange
} = usePickerSearch({showSearch, onSearchChange, getItemLabel, children, items});
} = usePickerSearch({showSearch, onSearchChange, children, items});
const {
multiDraftValue,
onDoneSelecting,
Expand All @@ -114,7 +109,6 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
value,
onChange,
pickerExpandableRef: pickerExpandable,
getItemValue,
topBarProps,
setSearchValue,
mode,
Expand All @@ -137,7 +131,6 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
const {label, accessibilityInfo} = usePickerLabel({
value,
items,
getItemLabel,
getLabel,
accessibilityLabel,
accessibilityHint,
Expand Down Expand Up @@ -166,8 +159,6 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
value: mode === PickerModes.MULTI ? multiDraftValue : value,
onPress: mode === PickerModes.MULTI ? toggleItemSelection : onDoneSelecting,
isMultiMode: mode === PickerModes.MULTI,
getItemValue,
getItemLabel,
onSelectedLayout: onSelectedItemLayout,
renderItem,
selectionLimit,
Expand All @@ -180,8 +171,6 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
value,
multiDraftValue,
renderItem,
getItemValue,
getItemLabel,
selectionLimit,
onSelectedItemLayout,
toggleItemSelection,
Expand Down
17 changes: 1 addition & 16 deletions src/components/picker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,6 @@ export interface PickerSearchStyle {
}

type PickerPropsDeprecation = {
/**
* @deprecated
* A function that extract the unique value out of the value prop in case value has a custom structure (e.g. {myValue, myLabel})
*/
getItemValue?: (value: PickerValue) => any;
/**
* @deprecated
* A function that extract the label out of the value prop in case value has a custom structure (e.g. {myValue, myLabel})
*/
getItemLabel?: (value: PickerValue) => string;
/**
* @deprecated
* Callback for modal onShow event
Expand Down Expand Up @@ -314,10 +304,6 @@ export interface PickerItemProps extends Pick<TouchableOpacityProps, 'customValu
* Custom function for the item label (e.g (value) => customLabel)
*/
getItemLabel?: (value: PickerValue) => string;
/**
* @deprecated Function to return the value out of the item value prop when value is custom shaped.
*/
getItemValue?: PickerProps['getItemValue'];
/**
* Render custom item
*/
Expand Down Expand Up @@ -346,8 +332,7 @@ export interface PickerItemProps extends Pick<TouchableOpacityProps, 'customValu
testID?: string;
}

export interface PickerContextProps
extends Pick<PickerProps, 'value' | 'getItemValue' | 'getItemLabel' | 'renderItem' | 'selectionLimit'> {
export interface PickerContextProps extends Pick<PickerProps, 'value' | 'renderItem' | 'selectionLimit'> {
onPress: (value: PickerSingleValue) => void;
isMultiMode: boolean;
onSelectedLayout: (event: any) => any;
Expand Down