I came across a React Native issue last week, which initially got me rather confused as I thought it had to do with z-index positioning on Android. Turns out, there's an identified RN issue to do specifically, with how Pressable
child components are rendered on Android. Scroll to the bottom of this page if you're interested in more specifics.
To provide more context on my specific use case, I decided to try my hand at some diagrams. It's not the best, but hopefully does the job. 😛 If anyone has tips on how to export hand-drawn diagrams on an iPad to a Mac laptop, let me know.
The problem
Picture a mobile screen. At the bottom of the screen, I've got a React Navigation bottom tab navigator.
What I wanted was to have a darker overlay appear over the whole screen which would disappear upon being pressed i.e. a Pressable
overlay. Because of some other side effects, the trigger for showing / hiding the overlay needed to be done from the tab navigator.
To do this, I used the <Tab.Screen>
options
prop to pass in a custom tabBarIcon
, that includes not just the icon for the tab, but also a conditional Pressable
overlay which looks something like this... (note that I set the height and width of the overlay to take the full screen using the useWindowDimensions
hook from react-native
).
export default function TabNavigator() {
const windowDimensions = useWindowDimensions()
const [showOverlay, setShowOverlay] = React.useState < boolean > false
const tabScreenOptions = {
// other options
tabBarIcon: ({ focused, color }: { focused: boolean, color: string }) => {
return (
<>
// tab icon component
{showOverlay ? (
<Pressable
onPress={closeOverlay}
style={[styles.overlay, { height: windowDimensions.height, width: windowDimensions.width }]}
/>
) : null}
</>
)
},
}
return (
<Tab.Navigator>
// Other tab screens
<Tab.Screen component={TabScreen} name="Tab name" options={tabScreenOptions} />
</Tab.Navigator>
)
}
const styles = StyleSheet.create({
overlay: {
backgroundColor: 'black',
flex: 1,
opacity: 0.5,
position: 'absolute',
zIndex: 1,
},
})
When testing on iOS, all works as expected. Upon the showOverlay
state variable being set to true
, the full screen overlay appears and is pressable. 🎉
Android does not work as expected however, because Android only allows the overlapping area of the child (i.e. the overlay) and parent (tab navigator) to be pressable. In my case, because the tab navigator is smaller than the full screen overlay, I'm stuck. In order to mimic the behaviour seen in iOS, I'd need to make the tab navigator height and width to be that of the entire screen... which isn't a viable option in my case.
A potential solution
So what did I do? I decided to go down a slightly convoluted path in order to get more flexibility by using React context. As the tab navigator acts as the parent for all the screens and any child components present within the screens, I could set up a context provider at the tab navigator level and then put in place context consumers at whatever component I needed down the hierachy.
export function TabNavigator() {
const [showOverlay, setShowOverlay] = React.useState < boolean > false
export const OverlayContext = React.createContext({ closeOverlay: () => {}, showOverlay: false })
// const tabScreenOptions same as before
const overlayContext = {
closeOverlay,
showOverlay,
}
const closeOverlay = React.useCallback(async () => {
setShowOverlay(false)
// do some other things
}, [])
return (
<OverlayContext.Provider value={overlayContext}>
<Tab.Navigator>
// Other tab screens
<Tab.Screen component={TabScreen} name="Tab name" options={tabScreenOptions} />
</Tab.Navigator>
</OverlayContext.Provider>
)
}
To set up a consumer, in any child component of the TabNavigator
, I used the useContext
hook.
export function ChildComponent() {
const overlayContext = React.useContext(OverlayContext)
return (
<>
{Platform.OS === 'android' && overlayContext.showOverlay ? (
<Pressable
onPress={overlayContext.closeOverlay}
style={[styles.overlay, { height: windowDimensions.height, width: windowDimensions.width }]}
/>
) : null}
// the child component
</>
)
}
To be specific, I used the Platform
module from react-native
to check that this only shows up for Android operating systems. The styles.overlay
styling is the same as what I had previously.
Let me know what you think! I blog at https://bionicjulia.com and can be found on Twitter and Instagram.
Top comments (0)