I have been trying to tweak the styles and elements render order so that Android and iOS behave the same. Did a lot of research and the current answers don't seem to solve the issue.
The issue is this exact code works on iOS but not on an Android device.
class Dropdown extends React.Component {
constructor() {
super()
this.state = {
open: false,
selected: undefined,
}
}
handleSelectPress = () => {
this.setState({open: !this.state.open});
}
handleOptionPress = (item) => {
this.setState({selected: item, open: !this.state.open});
this.props.onSelectOption(item);
}
render () {
const { selected = {}, open } = this.state
const { placeholder = '', options = [] } = this.props
return (
<Select
onPress={this.handleSelectPress}
value={selected.name || placeholder}
open={open}
options={options}
onOptionPress={this.handleOptionPress} />
)
}
}
const shadowStyles = {
ios: {
shadowOpacity: 0.3,
shadowRadius: 3,
shadowOffset: {
height: 1,
width: 1,
},
},
android: {
elevation: 5,
},
}
class Select extends React.PureComponent {
render () {
const { value, open, options, onOptionPress } = this.props
const shadowStyle = shadowStyles[Platform.OS]
return (
<View style={[shadowStyle, styles.wrap]}>
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.selectContainer}>
<Text>{value}</Text>
</View>
</TouchableWithoutFeedback>
{open && <View style={styles.menu}>
{ options.map( (item, idx) => <Option value={item} key={idx} onPress={onOptionPress} />)}
</View>}
</View>
)
}
}
class Option extends React.PureComponent {
handlePress = () => {
this.props.onPress(this.props.value)
}
render () {
const { value } = this.props
return (
<TouchableOpacity onPress={this.handlePress}>
<View style={styles.optionContainer}>
<Text>{value.name}</Text>
</View>
</TouchableOpacity>
)
}
}
class App extends React.Component {
render() {
return (
<View style={styles.root}>
<Dropdown
placeholder="--- SELECT ---"
options={
[
{ name: "Press me!", id: "1" },
{ name: "No Me!", id: "2" },
{ name: "Cmon guys, here!", id: "3" },
]
}
onSelectOption={ (item) => console.log(`item pressed! ${item}`)}
/>
</View>
)
}
}
const styles = StyleSheet.create({
root: {
width: 360,
height: 640,
backgroundColor: '#292c2e'
},
wrap: {
position: 'relative',
zIndex: 10,
backgroundColor: 'rgb(73,75,77)'
},
selectContainer: {
padding: 16,
},
menu: {
backgroundColor: 'rgb(73,75,77)',
position: 'absolute',
top: '100%',
left: 0,
right: 0,
maxHeight: 300
},
optionContainer: {
padding: 16
}
});
React Native version - 0.59.3
Few things to note:
zIndex
so that the menu element is rendered on top of other elements (rendered after the dropdown component). TouchableHighlight
, TouchableOpacity
, TouchableWithoutFeedback
and for android only TouchableNativeFeedback
View
and a ScrollView
(initially was scroll)Select
that toggles the open
flag works properly on both Android and iOS so I'm pretty sure its narrowed down to an absolute positioned parent.You have to set position for Select
instead of Option
here is a snack: https://snack.expo.io/HkDHHo6YV
change wrap
and menu
style with given below:
wrap: {
position: 'absolute',
zIndex: 10,
width: "100%",
backgroundColor: 'rgb(73,75,77)',
},
menu: {
backgroundColor: 'rgb(73,75,77)',
maxHeight: 300,
},
The missing piece was to account for the space under the dropdown since it was completely absolutely positioned. I had to change the Select
component to render a "padding" element to account for the space.
class Select extends React.PureComponent {
render() {
const { value, open, options, onOptionPress } = this.props;
const shadowStyle = shadowStyles[Platform.OS];
return (
<>
<View style={{height: 60}}></View>
<View style={[shadowStyle, styles.wrap]}>
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.selectContainer}>
<Text>{value}</Text>
</View>
</TouchableWithoutFeedback>
{open && (
<View style={styles.menu}>
{options.map((item, idx) => (
<Option value={item} key={idx} onPress={onOptionPress} />
))}
</View>
)}
</View>
</>
);
}
}
Then adjusted the height of the styles.selectContainer
to be the same height 60
selectContainer: {
padding: 16,
height: 60
},
import * as React from 'react';
import {
Text,
Button,
View,
StyleSheet,
Platform,
TouchableWithoutFeedback,
TouchableOpacity,
} from 'react-native';
class Dropdown extends React.Component {
constructor() {
super();
this.state = {
open: false,
selected: undefined,
};
}
handleSelectPress = () => {
this.setState({ open: !this.state.open });
};
handleOptionPress = item => {
this.setState({ selected: item, open: !this.state.open });
this.props.onSelectOption(item);
};
render() {
const { selected = {}, open } = this.state;
const { placeholder = '', options = [] } = this.props;
return (
<Select
onPress={this.handleSelectPress}
value={selected.name || placeholder}
open={open}
options={options}
onOptionPress={this.handleOptionPress}
/>
);
}
}
const shadowStyles = {
ios: {
shadowOpacity: 0.3,
shadowRadius: 3,
shadowOffset: {
height: 1,
width: 1,
},
},
android: {
elevation: 5,
},
};
class Select extends React.PureComponent {
render() {
const { value, open, options, onOptionPress } = this.props;
const shadowStyle = shadowStyles[Platform.OS];
return (
<>
<View style={{height: 60}}></View>
<View style={[shadowStyle, styles.wrap]}>
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.selectContainer}>
<Text>{value}</Text>
</View>
</TouchableWithoutFeedback>
{open && (
<View style={styles.menu}>
{options.map((item, idx) => (
<Option value={item} key={idx} onPress={onOptionPress} />
))}
</View>
)}
</View>
</>
);
}
}
class Option extends React.PureComponent {
handlePress = () => {
this.props.onPress(this.props.value);
};
render() {
const { value } = this.props;
return (
<TouchableOpacity onPress={this.handlePress}>
<View style={styles.optionContainer}>
<Text>{value.name}</Text>
</View>
</TouchableOpacity>
);
}
}
export default class App extends React.Component {
render() {
return (
<View style={styles.root}>
<Dropdown
placeholder="--- SELECT ---"
options={[
{ name: 'Press me!', id: '1' },
{ name: 'No Me!', id: '2' },
{ name: 'Cmon guys, here!', id: '3' },
]}
onSelectOption={item => console.log(`item pressed! ${item}`)}
/>
<Text>hellof</Text>
<View style={{backgroundColor: "red", height: 250}} />
</View>
);
}
}
const styles = StyleSheet.create({
root: {
width: 360,
height: 640,
backgroundColor: '#292c2e',
marginTop: 100
},
wrap: {
position: 'absolute',
zIndex: 10,
width: "100%",
backgroundColor: 'rgb(73,75,77)',
},
selectContainer: {
padding: 16,
height: 60
},
menu: {
backgroundColor: 'rgb(73,75,77)',
maxHeight: 300,
},
optionContainer: {
padding: 16,
height: 60
},
});
There is a problem with your styles in styles.menu
not with zIndex.
You have given position:'absolute'
and top:'100%'
.
This means your child view is positioned as absolute to the parent view and your child view will start right after your parent view is completing because of top:"100%"
.
Conclusion: your options are rendering out of the parent. yes, they are visible because there are no other components below and more of that zIndex is higher. and you can not touch anything that is not withing the range of parent.
Solution:
See working snack here
in styles.menu, change these,
menu: {
backgroundColor: 'rgb(73,75,77)',
zIndex: 1005,
top: 0,
left: 0,
right: 0,
maxHeight: 300,
},
and you can remove zIndex: 1000 and 1005 also :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With