question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Best way to document a stable multi-dropdown nested view overlap / zindex solution?

See original GitHub issue

Hi 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:open
  • Created 2 years ago
  • Reactions:16
  • Comments:20 (4 by maintainers)

github_iconTop GitHub Comments

20reactions
mikehardycommented, Jul 14, 2021

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:

        <View
          style={[{
            flexDirection: 'row',
            justifyContent: 'space-evenly',}, 
            (Platform.OS === 'ios' ? {zIndex: areaOpen ? 1 : 0} : {})]
          }>
          <View style={{justifyContent: 'center', alignContent: 'flex-end'}}>
            <FontAwesome
              name="puzzle-piece"
              size={30}
              color={styles.blue.color}
            />
          </View>
          <View style={styles.filterTextViewStyle}>
            <Text style={{textAlign: 'left'}}>Área laboral:</Text>
          </View>
          <View style={{justifyContent: 'center'}}>
            <DropDownPicker
              open={areaOpen}
              value={areaValue}
              items={areaItems}
              setOpen={setAreaOpen}
              setValue={setAreaValue}
              setItems={setAreaItems}
              listMode="SCROLLVIEW"
              style={styles.dropDownStyle}
              zIndexInverse={7000}
              zIndex={1000}
              // containerStyle={{borderColor: 'green', borderWidth: 1}}
              dropDownContainerStyle={{width: 200, backgroundColor: 'white'}}
              listItemContainerStyle={{
                width: 200,
                // borderColor: 'red',
                // borderWidth: 1,
              }}
            />
          </View>
        </View>
7reactions
mikehardycommented, Jul 21, 2021

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 onOpen prop 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.

  const closeAllOpen = () => {
    fechaOpen && setFechaOpen(false);
    provinciaOpen && setProvinciaOpen(false);
    nivelOpen && setNivelOpen(false);
    discapacidadOpen && setDiscapacidadOpen(false);
    salarioOpen && setSalarioOpen(false);
    tipoOpen && setTipoOpen(false);
    areaOpen && setAreaOpen(false);
    return true;
  };
Read more comments on GitHub >

github_iconTop Results From Across the Web

Fix z-index with overlapping dropdowns - css - Stack Overflow
Set the z-index of the class you're adding on click higher than the z-index of the existing elements. const dropdowns = document.
Read more >
Stacking overlapping views with zIndex in Expo and React ...
Learn how to stack overlapping views with zIndex. zIndex is the Expo and React Native analog of CSS's z-index property which lets the...
Read more >
Dropdown Menus with More Forgiving Mouse Movement Paths
Use larger, more forgiving pseudo-elements (or an overlapping margin on the submenu) with say a .1s transition. That way you can clip the ......
Read more >
CSS Grid Layout Module Level 2 - W3C
Note: There are multiple ways to specify the structure of the grid and ... the ability to overlap content and control layering with...
Read more >
Drop-Down Menus, Horizontal Style - A List Apart
Better still, for code-wary designers, no JavaScript is required! (Actually, a tiny bit of JavaScript is needed, but it's not what you think.)...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found