-
Notifications
You must be signed in to change notification settings - Fork 24.8k
Closed
Labels
Resolution: LockedThis issue was locked by the bot.This issue was locked by the bot.
Description
I am trying to render elements conditionally where each element has different zIndex style property.
Using folowing code in Android emulator with react-native 0.30.0.
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class Demo extends Component {
constructor(props, ctx) {
super(props, ctx);
this.state = {
showGreen: true,
};
}
render() {
return (
<View style={{flex: 1, padding: 20}}>
<View style={[styles.item, {zIndex: 3, backgroundColor: 'red'}]}>
<Text>zIndex: 3</Text>
</View>
{this.state.showGreen &&
<View style={[styles.item, {zIndex: 2, backgroundColor: 'green'}]}>
<Text>zIndex: 2</Text>
</View>
}
<View style={[styles.item, {zIndex: 1, backgroundColor: 'blue'}]}>
<Text>zIndex: 1</Text>
</View>
<View style={styles.button}>
<Text onPress={() => this.setState({ showGreen: !this.state.showGreen })}>
Toggle green
</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
item: {
marginTop: -20,
height: 50,
paddingTop: 22,
},
button: {
backgroundColor: 'gray',
marginTop: 30,
}
});
Initial screen looks like expected:
When I click 'Toggle green' button to dynamically show/hide green element it hides blue element instead and furthermore element's text is missing:
When I click the button again, red and green elements remains visible and the toggle button jumps down.
thedgbrt, hlynn93, roysG, FuzzyTree, tioback and 37 more
Metadata
Metadata
Assignees
Labels
Resolution: LockedThis issue was locked by the bot.This issue was locked by the bot.
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
thedgbrt commentedon Aug 30, 2016
Still an issue in 0.32.
It seems that zindex breaks component unmounting.
roysG commentedon Sep 7, 2016
any news?
tioback commentedon Sep 19, 2016
Same thing happened to me, so I'll throw in some more details:
In iOS, returning
null
makes the element disappear.In Android, you have to reduce the height to 0 and remove borders.
What is worse is that you can't stick to a single solution, because Android's workaround won't work for iOS. The element will just show up again.
Here's the code for a component that does this sort of thing:
dmitru commentedon Sep 30, 2016
+1, happened to me in a similar situation.
liamfd commentedon Oct 7, 2016
If it's helpful to anyone, I created a
Hideable
component to handle this:Here's a simple use:
Here's a more complex, interactive usage example:
You should be able to pass through whatever styles or props you'd like to
Hideable
'sView
, make it animatable, etc.Also, @tioback, I noticed you set a negative
zIndex
, is that something you recommend? It's worked for me without it but I wasn't sure if it was needed for certain devices or certain use cases. I also noticed that on my device I had to remove the vertical margins and padding as well, so there may be other styles to watch out for.I'm still pretty new to React Native so, grain of salt.
tioback commentedon Oct 7, 2016
@liamfd no, I don't recommend for, nor against it. Might be some left-over from a failed test.
Nice component, BTW.
asgvard commentedon Nov 10, 2016
Hi,
TL;DR;
If you have array of components with zIndexes, and then one of them dynamically removed (becomes null by state change for ex.), instead of reordering the remaining components, the removed one still "partially" remains in the viewport and covers everything underneath by white area.
Details:
I'm struggling with the same issue. Afaik, zIndex only reorders the Views on native, but seems that whenever the element becomes null (removed from the tree), it still overlaps all the elements that had lower zIndex at some point, even though this "newly becoming null" element doesn't have it's own zIndex as it doesn't have styles anymore.
So this means that the element is not removed completely when it's set to null, because the green color in the initial example still stays.
It's a huge problem for me, because I'm building cross-platform app and I'm trying to have as much shared code between Web and Native as possible. I'm using react-router, so I have few
<Match>
components as siblings. To implement animated transitions between pages I'm using react-motion, that basically sets component to "null" at the end of some spring animation when it's not matching the route. Assuming that I cannot control the order of<Match>
components (because they're in a separate route config which is again shared between Web and Native), it causes the issue that if I go from the last page to previous one, the last one becomes "null" at the end of animation and jumps to the front layer. All pages are absolutely positioned and fullscreen, so in result I see the full white screen, because this "null" element overlaps everything. It's not a best option for me to use workaround like<Hideable>
, because it's platform agnostic, and my goal is to have this animated transitions as a shared component, as well as the react-router<Match>
'es.Here is slightly modified example using three absolutely positioned elements, each subsequent of them have lower zIndex, so red is on top (60px height), green is underneath (90px height), and the blue is underneath (120px height). When the green is removed, the expected behaviour would be that green is completely removed from subviews, the remaining two elements would have their zIndex (order) recalculated, so we would see only red and blue underneath it.
But instead the green one stays alive, and even covers the blue element with "invisible" white overlay. (The same I see in my app with the routes described above). And it occurs ONLY when dynamically remove this green element. If I initially set the flag to false, it doesn't appear which is correct.
Modified example in the fresh react-native app 0.37.0:
Starting page:

Result:

Expected behaviour:

Not sure how I will proceed with this issue, but I will keep posting any ideas if I find them.
Update 1: If I initially put the showGreen to false, then add it by toggling the button, everything is fine. I suspect that the views are reordered by zIndex only when they're added, but not when they're removed. Might it be related to this commit ? In the ViewGroupManager.java and ReactViewManager.java the reordering is not called on removeViewAt, might this be a problem? I'm not a Java expert though, would appreciate if someone could have a closer look at this particular case.
tuckerconnelly commentedon Nov 14, 2016
Yo I'm the author of that commit :)
@asgvard Thanks for the fantastic bug reporting. I think you're right, check out:
react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java
Line 123 in 9ee815f
I think the fix is as simple as matching the
addView
method so it looks like:I don't really have the bandwidth to PR a fix and test it right now, but you probably could :) It's a good first PR.
asgvard commentedon Nov 15, 2016
@tuckerconnelly Hey :) So today I made a try to fix this issue with simply adding the
reorderChildrenByZIndex(parent);
inremoveViewAt
forViewGroupManager
, but unfortunately it didn't helped. As I mentioned I'm not a Java developer, so I took some simple steps to debug this by using Logs. I added aaccessibilityLabel
to my<View>
's so I can easily see which one is logged in Java code withview.getContentDescription()
.Here is my modified JS code:
The result leads me to even more deep problem. From JS prospective I'm removing Green view, but in Java it actually tries to remove Blue:
Seems that in
NativeViewHierarchyManager
in the methodmanageChildren
it uses indexes to remove views, but since the indexes changed internally in Java (after reordering by zIndex from[RED, GREEN, BLUE, BUTTON]
to[BUTTON, BLUE, GREEN, RED]
), and JS doesn't know about it, maybe it's more reliable to remove by tags? Because tagsToDelete contain the correct one, which is 8 (GREEN), and 12 is BLUE.Also this bug might lead to another unexpected things because
dropView
is still called on GREEN :) So basically the BLUE isremoved
from view manager, but GREEN get dropped.Will post any updates.
Cheers
asgvard commentedon Nov 15, 2016
UPDATE:
First of all, I found out that it has nothing to do with the reorderChildrenByZIndex() at all, because we actually don't need to reorder children after we remove something, because the relative zIndex order for remaining items remain the same. The problem was exactly in that it was removing the wrong
View
by it's index in the array.Fixed by relying on
tagsToDelete
instead ofindicesToRemove
inmanageChildren
method ofNativeViewHierarchyManager
. Because after reordering we cannot rely on the item index in theViewGroup
, but thetag
seems to be reliable way of identifying theView
anytime. I think the similar reason of usingtagsToDelete
was applied here.Also this issue happens when Adding views, it adds them in the wrong index :) But since after adding the views we're doing reorder again, it doesn't matter that it was added into the wrong place.
So the proposed solution is to change
manageChildren
method, so it will first make a loop bytagsToDelete
, performremoveView()
on all thetagsToDelete
, then after the cleanup of all the nodes to delete, we perform a loop byviewsToAdd
and that's it. It will remove the huge chunk of code from here to here, because essentially all it's doing isviewManager.removeViewAt(viewToManage, indexToRemove);
only in the case if the node is not animated and not in thetagsToDelete
array. So instead we could just move this responsibility to the loop bytagsToDelete
.I will prepare PR soon :)
93 remaining items