FlatList adds extra View around each row

The problem

The high-level summary of my problem is that I need to give one row of a FlatList a higher z-index than the rest. There are two factors at play in the internal implementation of react-native-web that make this task impossible:

  1. Each view creates its own stacking index.
  2. It seems like react-native-web internally wraps each row with a View somewhere, because in the DOM each row is wrapped in a div.

Therefore, even after giving one row of the FlatList a higher z-index than the rest in renderItem, that z-index will have no effect, because react-native-web wraps each row in a view that creates it’s own stacking context.

How to reproduce

Simplified test case: https://codesandbox.io/s/flamboyant-pond-g8k6j?file=/src/App.js

Steps to reproduce:

  1. Create a FlatList component where renderItem uses absolute positioning to overlap part of its content over another row.
  2. Set the zIndex of the item you want to show on top.
  3. Notice that the zIndex has no effect.

Expected behavior

Altering the zIndex of a row in a FlatList should work.

Environment (include versions). Did this work in previous versions?

  • React Native for Web (version): ^0.14.1
  • React (version): ^16.13.1
  • Browser: Google Chrome

Some background on the problem

I am working on a cross-platform chat app, and we’re using a FlatList component to render the messages in a chat:

image

I am trying to build a context menu for each message, that appears on hover and looks something like this:

image

So I implemented this by giving each row an absolute-positioned menu, and displaying it if the row is hovered. So far so good, but there’s a problem where long rows of text will be covered by the menu:

image

To fix this, I tried to give the menu a negative value for top to shift it upwards, but it’s covered by another row:

image

So what I need to do to fix that is give the hovered row (which is currently displaying the menu) a higher z-index than the other rows. That works if I manually adjust the style from the chrome dev console:

image

So I built this out programmatically, such that the renderItem function of the FlatList will give the outermost view of each row a z-index of 1, while all others have a default z-index of 0. However, this didn’t solve my problem.

Workarounds

I can include the floating context menu outside the FlatList and absolute-position it over the row that’s hovered. However, that has a performance impact on the application because it requires that I call measureLayout in response to scroll events. It also results in the menu “wiggling around”, since there is a delay in between the FlatList re-rendering with its updated scroll position and measureLayout completing and re-rendering the menu.

Potential Solution

I don’t claim to be an expert on the internals of react-native-web, so I’m sure you’ll have a much better idea of what a solution might look like, but here are three potential suggestions I can think of:

  1. In react-native-web, the View component has a default zIndex of 0 so that each view creates its own stacking context. You could make an exception and give the View created for each row within FlatList a zIndex of auto, so that it doesn’t create a stacking context.
  2. Maybe you could refactor the FlatList so instead of using a View internally it uses React.cloneElement?
  3. You could offer an extra prop to the FlatList so that users can specify a stacking order/zIndex for each row by index or something?

One last note:

If you include the inverted prop on the FlatList, then the overlap works as expected when a row overlaps the next row down, but not for the next row up. If you do not include the inverted prop on the FlatList, then the overlap works as expected when a row overlaps the next row up, but not the next row down.

1 possible answer(s) on “FlatList adds extra View around each row

  1. Okay, updated the codesandbox with a solution that worked for me. This wasn’t a bug, just an intricacy I didn’t understand. CellRendererComponent was the trick, so thanks for the help! I think this is safe to close.