I need to make a standalone (without additional bars or else) menu button in my React Native (v 0.47.2) Android app:
On touch it has to open side menu:
Which component I need to use?
You are using react-navigation. Use the StackNavigator. The StackNavigator can set Headers. In the Header, there is a prop to which you can pass an Icon (or any Component).
Here an example:
// all your other imports
import Icon from "react-native-vector-icons/Ionicons";
import {
Platform,
} from "react-native";
const MenuButton = ({ navigate }) => {
return (
<Icon
name={Platform.OS === "ios" ? "ios-menu-outline" : "md-menu"}
onPress={() => navigate("DrawerOpen")}
/>
)
}
StackNavigator({
Notifications: {
screen: Example,
navigationOptions: ({ navigation }) => ({
headerLeft: <MenuButton {...navigation} />,
}),
},
The headerLeft (or headerRight) can be used for you case (Documentation). Here I pass <MenuButton /> component. You can set the color of the StackNavigators Header to the backgroundColor of your app, or transparent. That way, there won`t be anything visible, but the menu button.
Of yourse you would need to stack you StackNavigator in a DrawerNavigator for the onPress={() => navigate("DrawerOpen")} to work.
In the DrawerNavigator you can use a contentComponent which you pass your custom component, that contains your menu.
Here is a more complex setup http://rationalappdev.com/cross-platform-navigation-in-react-native/
Related
TouchableOpacity onPress is not working inside Flatlist but when I replaced onPress with onPressIn/onPressOut it is working fine, but in that case the reaction is too fast and having issue while scrolling. I don''t know what it is happening and haven't found any related issue. Below is my code:
renderItem = ({ item, index }: { item: any, index: number }) => {
const { type } = this.props;
const valueType = {
phone: item,
stage: item.title,
location: item.name
}
return (
<TouchableOpacity
onPressIn={() => this.onSelect(item, index)}
style={styles.modalListContainer}
>
<Icon name={icon[type]} height={20} width={20}/>
<Spacer width={10} />
<View style={styles.modelTextContainer}>
<Text style={styles.modelText}>{valueType[type]}</Text>
</View>
</TouchableOpacity>
)
}
<FlatList
data={item}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={() => <><Spacer height={10} /><View style={styles.modelTextDevider} /><Spacer height={10} /></>}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.container}
/>
It is rendered inside a Modal using react-native-modals library. Any help would be appreciated.
Thank you.
react-native-modals, have a parent touchable component (PanResponder) which wraps your children's components. On some android devices, when you have a touchable component like a button, the touch event does not propagate down to child component instead capture by react-native-modals parent component.
The ideal solution should be absolute positioning your button but will break your UI and the modal will be useless.
There's an existing issue with this library repository.
https://github.com/jacklam718/react-native-modals/pull/210
but the solution provided is not 100% accurate for Android devices.
If you're using React Navigation, you already installed react-native-gesture-handler.
import TouchableOpacity from react-native-gesture-handler in place of `react-native. It should solve the issue for most devices.
I am trying to build an auth flow that has a welcome page and then login or signup depending on the user's case.
It is built inside a stack navigator. The first screen has no header but then login and signup do via their screen options.
// AuthNavigator
const AuthStackNavigator = createStackNavigator();
export const AuthNavigator = () => {
return (
<AuthStackNavigator.Navigator
initialRouteName={WELCOME_PAGE.id}
screenOptions={{
headerShown: false,
headerTintColor: colors.primary,
}}
lazy>
<AuthStackNavigator.Screen
name={WELCOME_PAGE.id}
component={WELCOME_PAGE.component}
/>
<AuthStackNavigator.Screen
name={LOGIN_PAGE.id}
component={LOGIN_PAGE.component}
options={LOGIN_PAGE.options}
/>
<AuthStackNavigator.Screen
name={SIGN_UP_PAGE.id}
component={SIGN_UP_PAGE.component}
options={LOGIN_PAGE.options}
/>
</AuthStackNavigator.Navigator>
);
};
This flow, is nested inside a tabNavigator:
// AuthTabNavigator
const AuthTabNavigator =
Platform.OS === 'android'
? createMaterialBottomTabNavigator()
: createBottomTabNavigator();
const RootNavigator = () => {
return (
<AuthTabNavigator.Navigator
activeColor={colors.primary}
inactiveColor="grey"
barStyle={materialBottomNavBackgroundColor}
tabBarOptions={defaultTabNavOptions}>
<AuthTabNavigator.Screen
name={WELCOME_PAGE.id}
component={AuthNavigator}
options={WELCOME_PAGE.options}
/>
</AuthTabNavigator.Navigator>
);
};
export default RootNavigator;
On iOS, things work fine but on Android, It has weird behaviour. When pressing on the input field, the keyboard pushes the bottom bar causing a clunky effect on press but also on dismiss of the field. As if it were recalculating the height every time and repositioning the layout.
In order to make sure it's not coming from my code, I tested again with the snippet from the React Native documentation
I get the following (these are on the Android emulator but I get the same result on my OnePlus android phone)
base version: https://recordit.co/tcEwDbo1oT
no header version: https://recordit.co/O4lZ9G83vg
not nested in a tab navigator: https://recordit.co/uh7mOGlKdk
The only version that works is the one when not nested in the tab navigator so I guess the issue comes from there.
I checked a few solutions but none worked:
Android manifest
Issue coming from the header
Another
workaround because of header
I am using React Native CLI:
"react-native": "0.62.2",
"#react-navigation/bottom-tabs": "^5.2.8",
"#react-navigation/drawer": "^5.7.2",
"#react-navigation/material-bottom-tabs": "^5.1.10",
"#react-navigation/native": "^5.1.7",
"#react-navigation/stack": "^5.2.17",
Let me know if you encountered the same issue and found a way to fix it. Thanks in advance.
EDIT: SOLUTION I WENT WITH
On top of the "custom" solution I posted in the comments, after opening an issue on React Navigation repo and checking closed issues, I found that on Android, you might have 2 options:
Either your invert the nesting, see here
Or you can set the behaviour of the keyboardAvoidingView to position on Android and provide a custom Tabbar
https://github.com/react-navigation/react-navigation/issues/7359#issuecomment-545842090
https://stackoverflow.com/a/51169574/11287266
I ended up going with the latter:
const TabBarComponent = props => {
return (
<View collapsable={false}>
<BottomTabBar {...props} />
</View>
);
};
// AuthTabNavigator
const AuthTabNavigator = createBottomTabNavigator();
const RootNavigator = () => {
return (
<AuthTabNavigator.Navigator
activeColor={colors.primary}
inactiveColor="grey"
barStyle={materialBottomNavBackgroundColor}
tabBarOptions={defaultTabNavOptions}
tabBar={props => <TabBarComponent {...props} />}>
<AuthTabNavigator.Screen
name={WELCOME_PAGE.id}
component={AuthNavigator}
options={WELCOME_PAGE.options}
/>
</AuthTabNavigator.Navigator>
);
};
export default RootNavigator;
<KeyboardAvoidingView
contentContainerStyle={{ flexGrow: 1 }}
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'position'}
keyboardVerticalOffset={Platform.OS === 'android' ? -50 : 25}>
I have a #react-navigation/bottom-tabs navigator when my app opens whose contents are like:
<Tab.Navigator
tabBarOptions={{
activeTintColor: '#77dd77',
inactiveTintColor: 'gray',
}}
tabBar={props => <MyTabBar {...props} />}
backBehavior={"history"}
>
<Tab.Screen
name="Home"
component={Home}
options={{ title: 'Home' }}
/>
<Tab.Screen
name="Orders"
component={Orders}
options={{ title: 'Orders' }}
/>
<Tab.Screen
name="Profile"
component={Profile}
options={{ title: 'Profile' }}
/>
</Tab.Navigator>
I have a BackHandler in my code that makes the app exit when back button is pressed from the home page. Everything is fine and I have checked that the backhandler gets called when back button is pressed.
But when I switch to any other tab and then return to the homepage and press back to exit the app, backhandler stops working and the app shows error "The action 'GO_BACK'was not handled by any navigator. Is there any screen to go back to?"
This is a development-only warning but in the signed version, the app doesn't show any error and doesn't even exit.
How can I address this 'GO_BACK' action?
I was facing a similar issue, just found out the solution.
The problem was that I was trying to handle backHandler from the screen itself, but it just doesn't work like that with tab navigator (and maybe with react-navigation as a whole? I dunno).
Any way, you just need to add a listener for 'focus' (~componentDidMount) and 'blur' (~componentWillUnmount) like this:
<Tab.Screen name="Home" component={HomeScreen}
listeners={{ focus: () => BackHandler.addEventListener('hardwareBackPress',handleBackButton)
,blur: () => BackHandler.removeEventListener('hardwareBackPress',handleBackButton)
}}
/>
Where:
function handleBackButton () {
BackHandler.exitApp();
return true;
}
Now the backHandler function works in this way only in HomeScreen, and as usual in other screens.
React Navigation (version 5) has already handled the native back button on bottom tabs.
I was stuck with this issue for one day and tried so many ways.
I ended up finding out in react-navigation version_5 that they have included it with just one line.
https://reactnavigation.org/docs/bottom-tab-navigator/#backbehavior
Add the prop backBehaviour = "initialRoute" to your <Tab.Navigator/> and that will handle all native back button on tabs
<Tab.Navigator backBehaviour = "initialRoute" >
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
I was using backhandler in all my tabs to create a navigation flow I wanted. Turns out this was creating the issue. After removing the backhandlers from the rest tabs, the app runs smoothly now.
I am now trying to experiment more with backBehavior in tab navigator to try to get the flow I want.
Writing my problem here gave me a clearer view of my problem!
My answer is not related to your question. But I hope it will help you. I created a custom hook to handle back press in all bottom tabs.
// back handler hook
import React from 'react';
import {useEffect} from 'react';
import {BackHandler} from 'react-native';
export const useBackButton = (props, handler) => {
useEffect(() => {
props.navigation.addListener('focus', () => {
BackHandler.addEventListener('hardwareBackPress', handler);
});
props.navigation.addListener('blur', () => {
BackHandler.removeEventListener('hardwareBackPress', handler);
});
}, [handler]);
};
Now In home screen I added this
const onBackPress = () => {
BackHandler.exitApp();
return true;
};
useBackButton(props, onBackPress);
And In other screens I added following.
const onBackPress = () => {
props.navigation.goBack();
return true;
};
useBackButton(props, onBackPress);
Is there a way to add a clickable icon along with StackNavigator objects?
This is a working code and it will show a "createBottomTabNavigator" with only one icon that will lead to "OtherScreen" on press.
const OtherStack = createStackNavigator({
Other: OtherScreen,
});
OtherStack.navigationOptions = {
tabBarLabel: 'Other',
tabBarIcon: ({
focused
}) => (
<TabBarIcon focused={focused} name='archive' />
),
};
export default createBottomTabNavigator({
OtherStack
});
I would like to add a share icon to the same "createBottomTabNavigator" so it will be aligned with all other icons but I don't want it to lead to a different screen. I just want it to open a share dialog.
I can't find a solution for this. Is it possible at all? Can someone help please?
You can add a button to the tab bar that doesn't lead to another screen by overriding tabBarOnPress for the route. Here's an example: https://snack.expo.io/#notbrent/playful-almond
The important part is here:
Placeholder: {
screen: () => null,
navigationOptions: {
tabBarOnPress: ({ navigation, defaultHandler }) => {
// don't call default handler!
alert('pressed placeholder!');
},
},
},
I'm using react-navigation to build my first react-native app. I have a home screen on which I want to have a sign up button.
In my router file, I have the following:
export const RootNavigator = StackNavigator({
Home: {
screen: Home
},
SignUp: {
screen: SignUp
}
}, {
mode: 'modal',
headerMode: 'none',
}
);
Here is my Home.js file:
export default class Home extends Component {
render() {
const buttons = ['SIGN IN', 'SIGN UP']
const { selectedIndex } = this.state
return (
<View style={styles.logoContainer} >
<Button
raised
backgroundColor="#333"
title='SIGN IN' />
<Button
raised
onPress = {this.props.navigation.navigate('SignUp')}
backgroundColor="#333"
title='SIGN UP' />
</View>
);
}
}
In my index file, I render this RootNavigator component.
When I load up the simulator, the navigation goes to the Home screen but immediately animates to the SignUp screen.
I'm quite new to this but what I want to be able to do is have the home screen appear, and be able to navigate to the sign up screen when I click on the Sign Up button (which is why I have the Sign up screen in the StackNavigator).
Any thoughts on how to accomplish this?
You are executing function rather than passing as a property. Parenthesizes execute functions.
This,
onPress={this.props.navigation.navigate('SignUp')}
should be like below
onPress={() => this.props.navigation.navigate('SignUp')}
Your problem is not that you need more navigators. You have defined your onPress as the result of this.props.navigation.navigate('SignUp') however what you need is a function handle.
Put the following there and it will work as you intended;
() => this.props.navigation.navigate('SignUp')