Best way to document a stable multi-dropdown nested view overlap / zindex solution?
See original GitHub issueHi there 👋
This library is great, but it suffers from zIndexing / overlap issues that are basically out of it’s control. There are issues related to it many times in the repo, I imagine it’s a real bummer to deal with because it’s just the way react-native lays out view elements.
But I think there’s a solution that is really more about documentation than a technical fix.
Background, confirmed via testing on react-native 0.64.2 on real iOS device, iOS simulator and Android emuator
- on react-native (and in the web with standard CSS) - zIndex only applies to things in the same “stacking context”
- on react-native each view gets it’s own stacking context! So if you nest elements (like dropdowns from this library) in their own View elements for styling, the zIndex/zIndexInverse props here don’t matter, they’re in separate stacking contexts. (if the dropdowns are siblings - not in separate views - it works, which confuses people)
- on react-native android there is a “collapsable” View property that is true by default and it means that views that are only used for layout but don’t draw anything are pruned from the native tree, so even more confusing, your zIndex/zIndexInverse props work on Android even if dropdowns are nested in separate views! What a nightmare to explain.
So what to do?
The solution I just tested, and it seems to work on both platforms is this:
- if you are not nesting the dropdowns in separate views, keep on using zIndex/zIndexInverse - it works when the dropdowns are layout siblings because they share a stacking context on both platforms
- if you are nesting the dropdowns in separate views,
stop using zIndex/zIndexInverse (it won’t work on iOS and only works because of the “collapsable” memory optimization on Android) and instead set a zIndex prop on the parent Views dynamically, based on open state of the dropdown. If it’s open, set zIndex to 1, if it is not open, set it to 0. Works?[edit: see below comment - on iOS zIndex/zIndexInverse does no harm, it is required and works on Android, and parent-view zIndex is required on iOS but breaks Android, so it requires a platform-specific include of parent-view zIndex]
Here’s an App.js that should show it working even with some dropdowns going up and some going down.
function getItemsArray() {
return [{label: 'Apple', value: 'apple'},
{label: 'Banana', value: 'banana'},
{label: 'Cranberry', value: 'cranberr'},
{label: 'Durian', value: 'durian'},
{label: 'Eggplant', value: 'eggplant'},
]
}
const App = () => {
const [open, setOpen] = useState(false);
const [value, setValue] = useState(null);
const [items, setItems] = useState(getItemsArray());
const [open2, setOpen2] = useState(false);
const [value2, setValue2] = useState(null);
const [items2, setItems2] = useState(getItemsArray());
const [open3, setOpen3] = useState(false);
const [value3, setValue3] = useState(null);
const [items3, setItems3] = useState(getItemsArray());
const [open4, setOpen4] = useState(false);
const [value4, setValue4] = useState(null);
const [items4, setItems4] = useState(getItemsArray());
const [open5, setOpen5] = useState(false);
const [value5, setValue5] = useState(null);
const [items5, setItems5] = useState(getItemsArray());
const [open6, setOpen6] = useState(false);
const [value6, setValue6] = useState(null);
const [items6, setItems6] = useState(getItemsArray());
const [open7, setOpen7] = useState(false);
const [value7, setValue7] = useState(null);
const [items7, setItems7] = useState(getItemsArray());
const [open8, setOpen8] = useState(false);
const [value8, setValue8] = useState(null);
const [items8, setItems8] = useState(getItemsArray());
const [open9, setOpen9] = useState(false);
const [value9, setValue9] = useState(null);
const [items9, setItems9] = useState(getItemsArray());
BootSplash.hide();
return (
<SafeAreaView style={{flex: 1}}>
<ScrollView style={{flex: 1, flexGrow: 1}}
contentContainerStyle={{flexGrow: 1}}>
<Text style={{flex: 1, color: 'black'}}>Hi</Text>
<View style={{zIndex: open ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open}
value={value}
items={items}
setOpen={setOpen}
setValue={setValue}
setItems={setItems}
/></View><View style={{zIndex: open2 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open2}
value={value2}
items={items2}
setOpen={setOpen2}
setValue={setValue2}
setItems={setItems2}
/></View><View style={{zIndex: open3 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open3}
value={value3}
items={items3}
setOpen={setOpen3}
setValue={setValue3}
setItems={setItems3}
/></View><View style={{zIndex: open4 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open4}
value={value4}
items={items4}
setOpen={setOpen4}
setValue={setValue4}
setItems={setItems4}
/></View><View style={{zIndex: open5 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open5}
value={value5}
items={items5}
setOpen={setOpen5}
setValue={setValue5}
setItems={setItems5}
/></View><View style={{zIndex: open6 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open6}
value={value6}
items={items6}
setOpen={setOpen6}
setValue={setValue6}
setItems={setItems6}
/></View><View style={{zIndex: open7 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open7}
value={value7}
items={items7}
setOpen={setOpen7}
setValue={setValue7}
setItems={setItems7}
/></View><View style={{zIndex: open8 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open8}
value={value8}
items={items8}
setOpen={setOpen8}
setValue={setValue8}
setItems={setItems8}
/></View><View style={{zIndex: open9 ? 1: 0 }}>
<DropDownPicker
listMode="SCROLLVIEW"
open={open9}
value={value9}
items={items9}
setOpen={setOpen9}
setValue={setValue9}
setItems={setItems9}
/></View>
</ScrollView>
</SafeAreaView>
);
};
I think the best way to show this would be an entire page explaining it, with examples of the dead-end solutions (maybe?) plus this one to demonstrate why things work depending on the situation. It may involve a change to the “multiple dropdown” page or replace it?
I’d be happy to try a more formal writeup but first I thought I’d just propose it as a thought to see what you think.
Thanks!
Issue Analytics
- State:
- Created 2 years ago
- Reactions:16
- Comments:20 (4 by maintainers)

Top Related StackOverflow Question
I’m nearly complete with the screen that prompted this deep-dive, and I noticed that even the above solution is not quite enough to be cross-platform. It certainly fixes the common issue on iOS caused by the dropdown in non-sibling views having unexpected overlap behavior.
However! If you apply the parent-container zIndex, then Android dropdowns are no longer touchable (as noted in other issues). But Android will continue to work correctly with the Dropdown zIndex/zIndexInverse props so long as the parent view container has no zIndex property at all.
So the complete, works on both platform solutions actually has zIndex/zIndexInverse as documented in the multiple dropdown guide here, but also has a parent View zIndex prop included dynamically, like this chunk of layout:
No idea who is reading any of this but this library is solid. It deserves more community helping. So, here’s the close function for multi-dropdowns, just call this as the
onOpenprop for the dropdowns, as in:onOpen{closeAllDropdowns())Note that these are all my open dropdown state / state-toggle names. Yours will differ, but the pattern is super simple. Tested --> working, onOpen is apparently called before setXXXOpen() so there is not a race condition in my testing.