react-native-sectioned-multi-select
Version:
a multi (or single) select component with support for sub categories, search, chips.
333 lines (283 loc) • 8.18 kB
Markdown
# Recipes
Examples of commonly requested functions/patterns. Required props/functions ommited for brevity.
## Ref
Some of these examples use a ref to the component like this:
Function component:
```JS
import React, { useRef } from 'react'
const App = () => {
const ref = useRef(null)
// ref.current._toggleSelector()
return (
<>
<SectionedMultiSelect
...
ref={ref}
/>
<Button onPress={() => ref?.current?._toggleSelector()} />
</>
)
}
```
Class component:
```JS
<>
<SectionedMultiSelect
...
ref={SectionedMultiSelect => this.SectionedMultiSelect = SectionedMultiSelect}
/>
<Button onPress={() => this.SectionedMultiSelect._toggleSelector()} />
</>
```
## Custom select text
```js
}
renderSelectText = () => {
const { selectedItemObjects } = this.state
return selectedItemObjects.length
? `I like ${selectedItemObjects
// list each selected item with custom separators.
.map((item, i) => {
let label = `${item.title}, `
if (i === selectedItemObjects.length - 2) label = `${item.title} and `
if (i === selectedItemObjects.length - 1) label = `${item.title}.`
return label
})
.join('')}`
// nothing selected
: 'Select a fruit'
}
```
Add the props:
```js
<SectionedMultiSelect
renderSelectText={this.renderSelectText}
onSelectedItemObjectsChange={(selectedItemObjects) =>
this.setState({ selectedItemObjects })
}
/>
```
## Limit selection count
A basic example of using the confirm button text to show the user how many items they have selected / can select
```js
// set the max you want
this.maxItem = 5;
...
onSelectedItemsChange = (selectedItems) => {
if (selectedItems.length >= this.maxItems) {
if (selectedItems.length === this.maxItems) {
this.setState({ selectedItems })
}
this.setState({
maxItems: true,
})
return
}
this.setState({
maxItems: false,
})
this.setState({ selectedItems })
}
```
Add the prop:
```js
<SectionedMultiSelec
confirmText={`${this.state.selectedItems.length}/${this.maxItems} - ${
this.state.maxItems ? 'Max selected' : 'Confirm'
}`}
/>
```
## Select or remove all items
Renders a button that removes all items if anything is selected, or selects all items if nothing is selected.
```js
SelectOrRemoveAll = () =>
this.SectionedMultiSelect && (
<TouchableOpacity
style={{
justifyContent: 'center',
height: 44,
borderWidth: 0,
paddingHorizontal: 10,
backgroundColor: 'darkgrey',
alignItems: 'center'
}}
onPress={
this.state.selectedItems.length
? this.SectionedMultiSelect._removeAllItems
: this.SectionedMultiSelect._selectAllItems
}>
<Text style={{ color: 'white', fontWeight: 'bold' }}>
{this.state.selectedItems.length ? 'Remove' : 'Select'} all
</Text>
</TouchableOpacity>
)
```
## Item icons
You can add custom icons next to individual items/sub items by adding them to your items.
For the example below you would add the prop `iconKey="icon"` to SectionedMultiSelect.
There are 3 ways to put a custom icon next to an item: locally required image, uri object, or icon component name.
```js
// This is how you can load a local icon
const icon = require('./icon.png');
const items = [
{
name: 'Fruits',
id: 0,
// using local imported image:
icon: icon,
// using a web uri:
icon: { uri: 'https://cdn4.iconfinder.com/data/icons/free-crystal-icons/512/Gemstone.png' },
//using material icons icon name (or your own icons if using iconRenderer)
icon: 'filter_vintage',
...
}
]
```
## Custom icon renderer
Create your icon function
```js
icon = ({ name, size = 18, style }) => {
// flatten the styles
const flat = StyleSheet.flatten(style)
// remove out the keys that aren't accepted on View
const { color, fontSize, ...styles } = flat
let iconComponent
// the colour in the url on this site has to be a hex w/o hash
const iconColor =
color && color.substr(0, 1) === '#' ? `${color.substr(1)}/` : ''
const Search = (
<Image
source={{ uri: `https://png.icons8.com/search/${iconColor}ios/` }}
style={{ width: size, height: size }}
/>
)
const Down = (
<Image
source={{ uri: `https://png.icons8.com/arrow-down/${iconColor}ios/` }}
style={{ width: size, height: size }}
/>
)
const Up = (
<Image
source={{ uri: `https://png.icons8.com/arrow-up/${iconColor}ios/` }}
style={{ width: size, height: size }}
/>
)
const Close = (
<Image
source={{ uri: `https://png.icons8.com/close-button/${iconColor}ios/` }}
style={{ width: size, height: size }}
/>
)
const Check = (
<Image
source={{ uri: `https://png.icons8.com/check-mark/${iconColor}android/` }}
style={{ width: size / 1.5, height: size / 1.5 }}
/>
)
const Cancel = (
<Image
source={{ uri: `https://png.icons8.com/cancel/${iconColor}ios/` }}
style={{ width: size, height: size }}
/>
)
switch (name) {
case 'search':
iconComponent = Search
break
case 'keyboard-arrow-up':
iconComponent = Up
break
case 'keyboard-arrow-down':
iconComponent = Down
break
case 'close':
iconComponent = Close
break
case 'check':
iconComponent = Check
break
case 'cancel':
iconComponent = Cancel
break
default:
iconComponent = null
break
}
return <View style={styles}>{iconComponent}</View>
}
```
Another example using a different icon font
```js
import Icon from 'react-native-vector-icons/SimpleLineIcons'
icon = ({ name, size = 18, style }) => {
switch (name) {
case 'search':
iconName = 'magnifier'
break
case 'keyboard-arrow-up':
iconName = 'arrow-up'
break
case 'keyboard-arrow-down':
iconName = 'arrow-down'
break
case 'close':
iconName = 'close'
break
case 'check':
iconName = 'check'
break
case 'cancel':
iconName = 'close'
break
default:
iconName = null
break
}
return <Icon style={style} size={size} name={iconName} />
}
```
Add the prop:
```js
<SectionedMultiSelect iconRenderer={this.icon} />
```
## Search adornment
Adds a button next to search bar when searching something that doesn't exist in the items.
The button adds a new item to the items list with the name being current search text.
```js
this.termId = this.items[this.items.length - 1].id;
handleAddSearchTerm = (searchTerm) => {
// this is not how you'd want to generate an id in a real scenario.
const id = (this.termId += 1);
if (
searchTerm.length &&
!(this.state.items || []).some((item) => item.title.includes(searchTerm))
) {
const newItem = { id, title: searchTerm };
this.setState((prevState) => ({ items: [...(prevState.items || []), newItem] }));
this.onSelectedItemsChange([...this.state.selectedItems, id]);
this.SectionedMultiSelect._submitSelection();
}
};
searchAdornment = (searchTerm) =>
return (searchTerm.length ? (
<TouchableOpacity
style={{ alignItems: 'center', justifyContent: 'center' }}
onPress={() => this.handleAddSearchTerm(searchTerm)}
>
<View style={{}}>
<Image
source={{ uri: 'https://png.icons8.com/plus' }}
style={{ width: 16, height: 16, marginHorizontal: 15 }}
/>
{/* <Icon size={18} style={{ marginHorizontal: 15 }} name="add" /> */}
</View>
</TouchableOpacity>
) : null);
```
Add the prop:
```js
<SectionedMultiSelect
searchAdornment={(searchTerm) => this.searchAdornment(searchTerm)}
/>
```