Adding Popup Menus to React-Native App With Redux

This post walks through the process of creating an animated main menu in a react-native application with Redux. This process results in a menu that does the following:

1. Display / hide based on variable kept in Redux store and altered using Redux actions

2. Animates on displaying and hiding

3. Closes the menu when the user touches the area outside the menu

4. Does not cover the Android app bar or application header

The main reason I created my own router in the first place was to allow me to arbitrarily display components on the screen. In my case, conditionally rendering the application’s main menu based on a boolean value kept in my Redux store provides the capability to display that main menu whenever I change that value to true. Also, this provides a simple pattern to add any arbitrary number of menus:

1. Create menu component

2. Place menu component at top level of application keeping in mind that items rendered later will be placed in front of those rendered earlier

3. Use a conditional rendering library like render-if to render a component based off a value kept in redux store

4. Display and hide menu by dispatching actions that change the value of the component to true and false

So far, this technique results in the menu literally popping in and out of view; one moment it’s not visible, and the next moment it is. While effective, this behavior is not visually appealing, and is an excellent candidate for animation. The react-native-animatable library https://github.com/oblador/react-native-animatable easily enables component animation based on events. This is very useful because the goal here is to animate the entry and exit of the component which correspond to the componentDidMount and componentWillUnmount lifecyle events.

Main Menu Component:

The following code renders my MainMenu component

return (
<Animatable.View
style={styles.animatiableFill}
ref={(component)=>this.animatable = component}
onPress={this.closeMenu.bind(this)}>
<TouchableHighlight
style={styles.touchableBackground}
onPress={this.closeMenu.bind(this)}>
<View style={styles.menuContainer}>
<Text style={styles.modalMessageTitle}>Main Menu</Text>
<Hr lineColor=’#b3b3b3' />
<View style={[styles.footerButtonContainer, styles.modalButtonContainer]}>
<View style={styles.buttonPadder}>
{signInRenderable}
</View>
<View style={styles.buttonPadder}>
<Button
onPress={this.closeMenu.bind(this)}
title=”Settings”
accessibilityLabel=”Settings”
color=’limegreen’/>
</View>
</View>
<Hr lineColor=’#b3b3b3' />
</View>
</TouchableHighlight>
</Animatable.View>
)

Animatable.View is the wrapper that connects a component with the react-native-animatable library. A ref is set so that the component can be referenced throughout the component class for animation invocation. Wrapping the component with a TouchableHighlight enables the component to respond when the user presses the screen outside of the menu. This gives the effect that touching the background area “outside” of the main menu closes the menu. The TouchableHighlight component is styled to have a partially transparent white backgroundColor so that it obscures the screen under it. Inside this TouchableHighlight is a view that contains the actual menu and its buttons. Also included is a variable signInRenderable that enables conditional rendering of “Sign In” and “Sign Out” buttons depending on whether the user is currently signed out or signed in to the application.

Once the component is wrapped in the Animatable.View, animating it is as easy as applying the desired effect during the appropriate lifecycle events:

componentDidMount() {
this.animatable.bounceIn(500);
}
componentWillUnmount() {
this.animatable.fadeOut(500);
}

My biggest “gotcha” was not initially styling the Animatable.View to fill the entire screen. Once I styled the Animatable.View as follows, and its child touchable highlight to fill all the available space, the technique worked well.

animatiableFill:{
top: Expo.Constants.statusBarHeight + globals.appBar.height.standard,
…StyleSheet.absoluteFillObject,
},

Note that I moved the top of the Animatable.View component down to avoid the Android application bar and my application’s header that I discussed in a previous post.

Reginald Johnson has maintained his passion for coding throughout his 20+ year career as an Officer in the United States Navy. He enjoys applying his training and experience in programming, Systems Engineering, and Operational Planning towards programming. Follow him Twitter @reginald3.