Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow empty options #42

Merged
merged 2 commits into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,9 @@ searchControls={
minHeight: 10,
paddingVertical: 10,
paddingHorizontal: 5,
width: '50%',
width: '70%',
textAlign: 'center',
backgroundColor: 'pink',
},
textInputContainerStyle: {
flex: 1,
Expand All @@ -315,7 +316,7 @@ searchControls={
},
textInputProps: {
placeholder: 'Search anything here',
placeholderTextColor: 'gray',
placeholderTextColor: 'white',
},
}
```
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
5 changes: 3 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ export default function App() {
minHeight: 10,
paddingVertical: 10,
paddingHorizontal: 5,
width: '50%',
width: '70%',
textAlign: 'center',
backgroundColor: 'pink',
},
textInputContainerStyle: {
flex: 1,
Expand All @@ -108,7 +109,7 @@ export default function App() {
},
textInputProps: {
placeholder: 'Search anything here',
placeholderTextColor: 'gray',
placeholderTextColor: 'white',
},
}}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CheckBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const CheckBox = ({
/>
)}
</View>
{label && (
{label && label !== '' && (
<Text
style={[
checkboxComponentStyles?.checkboxLabelStyle || checkboxLabelStyle,
Expand Down
8 changes: 5 additions & 3 deletions src/components/Dropdown/DropdownFlatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ const DropdownFlatList = ({
};

useEffect(() => {
scrollToItem(listIndex.itemIndex);
}, [listIndex]);
if (options?.length) {
scrollToItem(listIndex.itemIndex);
}
}, [listIndex, options]);

return (
<FlatList
Expand Down Expand Up @@ -71,7 +73,7 @@ const DropdownFlatList = ({
checkboxStyle, // kept for backwards compatibility
checkboxLabelStyle, // kept for backwards compatibility
checkboxComponentStyles,
checkboxComponent
checkboxComponent,
})
}
keyExtractor={(_item, index) => `Options${index}`}
Expand Down
9 changes: 6 additions & 3 deletions src/components/Dropdown/DropdownSectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ const DropdownSectionList = ({
};

useEffect(() => {
scrollToLocation(listIndex);
}, [listIndex]);
if (options?.length) {
scrollToLocation(listIndex);
}
}, [listIndex, options]);

return (
<SectionList
Expand Down Expand Up @@ -111,7 +113,8 @@ const DropdownSectionList = ({
})
}
renderSectionHeader={({ section: { title, data } }) =>
data.length > 0 && (
data?.length &&
title && (
<SectionHeaderTitle
title={title}
sectionHeaderStyle={listComponentStyles?.sectionHeaderStyle}
Expand Down
2 changes: 0 additions & 2 deletions src/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const Input = ({
style,
primaryColor,
textInputContainerStyle,
openModal,
...rest
}: any) => {
const [isFocused, setFocus] = useState(false);
Expand All @@ -30,7 +29,6 @@ export const Input = ({
]}
onFocus={() => {
setFocus(true);
openModal();
}}
onBlur={() => setFocus(false)}
value={value}
Expand Down
61 changes: 31 additions & 30 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
const [listIndex, setListIndex] = useState<{
sectionIndex?: number;
itemIndex: number;
}>({ itemIndex: 0 }); // for scrollToIndex in Sectionlist and Flatlist

if (!options || options.length === 0) {
throw new Error('Options array cannot be empty or undefined');
}
}>({ itemIndex: 0, sectionIndex: 0 }); // for scrollToIndex in Sectionlist and Flatlist

useEffect(() => {
setNewOptions(options);
Expand All @@ -92,7 +88,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
*==========================================*/

// check the structure of the new options array to determine if it is a section list or a
const isSectionList = newOptions.some(
const isSectionList = newOptions?.some(
(item) => item.title && item.data && Array.isArray(item.data)
);

Expand All @@ -102,17 +98,17 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
const modifiedSectionData = extractPropertyFromArray(
newOptions,
'data'
).flat();
)?.flat();

/**
*`modifiedOptions` should only be used for computations newOptions remains the default array.
* At this point modifiedOptions now has the same structure for both `FlatList` and `SectionList`
* `options` is the original array, it never changes. (Do not use except you really need the original array) .
* `newOptions` is a copy of options but can be mutated by `setNewOptions`, as a result, the value many change.
* `modifiedOptions` should only be used for computations. It has the same structure for both `FlatList` and `SectionList`
*/
const modifiedOptions = isSectionList ? modifiedSectionData : newOptions;

const optLabel = optionLabel || DEFAULT_OPTION_LABEL;
const optValue = optionValue || DEFAULT_OPTION_VALUE;
const optionsCopy = JSON.parse(JSON.stringify(options)); // copy of the original options array

/*===========================================
* Selection handlers
Expand Down Expand Up @@ -143,7 +139,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
};

const removeDisabledItems = (items: TFlatList) => {
return items.filter((item: TFlatListItem) => !item.disabled);
return items?.filter((item: TFlatListItem) => !item.disabled);
};

const handleSelectAll = () => {
Expand All @@ -153,8 +149,8 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
// don't select disabled items
const filteredOptions = removeDisabledItems(
isSectionList
? extractPropertyFromArray(optionsCopy, 'data').flat()
: optionsCopy
? extractPropertyFromArray(options, 'data').flat()
: options
);

if (!prevVal) {
Expand Down Expand Up @@ -184,7 +180,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
(selectedValues: any[]) => {
//if the list contains disabled values, those values will not be selected
if (
removeDisabledItems(modifiedOptions).length === selectedValues.length
removeDisabledItems(modifiedOptions)?.length === selectedValues?.length
) {
setSelectAll(true);
} else {
Expand Down Expand Up @@ -234,10 +230,10 @@ export const DropdownSelect: React.FC<DropdownProps> = ({

const regexFilter = new RegExp(searchText, 'i');

// Because the options array will be mutated after Search, we have to search with a copy of the original array
// Because the options array will be mutated while searching, we have to search with the original array
const searchResults = isSectionList
? searchSectionList(optionsCopy as TSectionList, regexFilter)
: searchFlatList(optionsCopy as TFlatList, regexFilter);
? searchSectionList(options as TSectionList, regexFilter)
: searchFlatList(options as TFlatList, regexFilter);

setNewOptions(searchResults);
};
Expand Down Expand Up @@ -300,18 +296,22 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
*==========================================*/
const setIndexOfSelectedItem = (selectedLabel: string) => {
isSectionList
? optionsCopy.map((item: TSectionListItem, sectionIndex: number) => {
item.data?.find((dataItem: TFlatListItem, itemIndex: number) => {
if (dataItem[optLabel] === selectedLabel) {
setListIndex({ sectionIndex, itemIndex });
? (options as TSectionListItem[] | undefined)?.map(
(item: TSectionListItem, sectionIndex: number) => {
item?.data?.find((dataItem: TFlatListItem, itemIndex: number) => {
if (dataItem[optLabel] === selectedLabel) {
setListIndex({ sectionIndex, itemIndex });
}
});
}
)
: (options as TFlatListItem[] | undefined)?.find(
(item: TFlatListItem, itemIndex: number) => {
if (item[optLabel] === selectedLabel) {
setListIndex({ itemIndex });
}
});
})
: optionsCopy?.find((item: TFlatListItem, itemIndex: number) => {
if (item[optLabel] === selectedLabel) {
setListIndex({ itemIndex });
}
});
);
};

return (
Expand Down Expand Up @@ -350,7 +350,6 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
modalProps={modalProps}
>
<ListTypeComponent
removeClippedSubviews={false}
ListHeaderComponent={
<>
{isSearchable && (
Expand All @@ -362,12 +361,14 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
textInputContainerStyle={
searchControls?.textInputContainerStyle
}
openModal={() => setOpen(true)} // There seems to a known issue on expo web that closes the modal when the input is focused
placeholder={
searchControls?.textInputProps?.placeholder || 'Search'
}
{...searchControls?.textInputProps}
/>
)}
{listHeaderComponent}
{isMultiple && modifiedOptions.length > 1 && (
{isMultiple && modifiedOptions?.length > 1 && (
<View style={styles.optionsContainerStyle}>
<TouchableOpacity onPress={() => {}}>
<CheckBox
Expand Down
2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

export const extractPropertyFromArray = (arr: any, property: string) => {
let extractedValue = arr.map((item: any) => item[property]);
let extractedValue = arr?.map((item: any) => item[property]);

return extractedValue;
};