React Navigation v.5 Tab Bar Icon Navigate to Modal - react-native

Anyone know a good alternative to tabBarOnPress in react navigation v.5? I want to navigate to a modal stack when a user presses a tabIcon, (i.e. cancel its default navigation behavior) but the icon seems to navigate to the Tab Screen first, then navigates to the modal.
For clarification, here is my PostIcon TabIcon Component
export const PostStackIcon: React.FC<TabBarIconProps> = ({ size, color }) => {
const navigation = useNavigation();
const goToCreatePost = () => {
navigation.navigate('CreatePostStack', { screen: 'CreatePost'});
}
return (
<TouchableWithoutFeedback onPress={() => goToCreatePost()}>
<Icon
name="Post"
width={size * 2}
height={size}
fillOpacity={0}
stroke={color}
secondaryStroke={color}
/>
</TouchableWithoutFeedback>
)
}

Official document
You can use listeners prop of Tab.Screen, this is the closest alternative to tabBarOnPress IMHO.
Below is quoted from the documents:
Sometimes you might want to add a listener from the component where you defined the navigator rather than inside the screen. You can use the listeners prop on the Screen component to add listeners. The listeners prop takes an object with the event names as keys and the listener callbacks as values.
Example:
<Tabs.Screen
name="Chat"
component={Chat}
listeners={{
tabPress: e => {
// Prevent default action
e.preventDefault();
},
}}
/>
You can also pass a callback which returns the object with listeners. It'll receive navigation and route as the arguments.
Example:
<Tabs.Screen
name="Chat"
component={Chat}
listeners={({ navigation, route }) => ({
tabPress: e => {
// Prevent default action
e.preventDefault();
// Do something with the `navigation` object
navigation.navigate('AnotherPlace');
},
})}
/>

Related

How do I pass certain route parameters from a screen function to a navigation header button in React Native

I am new to react native and am basically wondering how to pass information from one screen to another if the button that triggers the shift from one screen to another is in the navigation class.
For example, If I have a screen:
export default function Screen() {
const data = ["a", "b", "c"]
}
and another screen with a SectionList section that requires that information:
const data = [
{
title: "",
data: {route.params?.data},
},
and finally within the navigation tab a custom navbar header:
<Stack.Screen
name="Screen"
component={ChatScreen}
options={({ route }) => ({
headerRight: () => (
<Pressable onPress={() => navigation.navigate("Screen", {data: *get data from
Screen here*)}>
<Ionicons
name="ellipsis-horizontal"
size={25}
color="#5C6AEF"
style={{ marginBottom: 2 }}
/>
</Pressable>
),
})}
/>
basically, how do I call the information from Screen in the Navigation tab to pass to the SectionList screen?
Thanks for the help!

How do I add a navigation button to a React Navigation Stack header with nested Bottom Tab Navigator?

I am trying to build a mobile app in react-native and I'm having some problems setting up React Navigation.
What I want to achieve is a Bottom Tab Navigator that Navigates to the 'Home' screen and the 'Profile' Screen. From the 'Home' screen, there should be a button to navigate to the 'Settings' screen in the Header.
I have got to the point where I have a Bottom Tab Navigator that can successfully navigate between the 'Home' and 'Profile' screens, as well as a button on the header for the Settings screen using the Stack navigation header. However, I am having trouble navigating to the 'Settings' screen with this button.
My code for the Stack navigator is:
const MainStackNavigator = () => {
return (
<Stack.Navigator screenOptions={screenOptionStyle}>
<Stack.Screen
name="Home"
component={HomeScreen}
options = { ({navigation}) => ({
title: "Home",
headerStyle: {
backgroundColor: '#ff6600',
},
headerRight: () => (
<Button
onPress={() => navigation.navigate(SettingScreen)}
title="Settings"
color="#fff"
/>
)
})}
/>
<Stack.Screen name="Settings" component={SettingScreen} />
</Stack.Navigator>
);
}
When I click on the Settings button, I get the error:
"The action 'NAVIGATE' with payload undefined was not handled by any navigator.
Do you have a screen named 'SettingScreen'?"
Upon looking for a solution to this error I found this article: Nesting Navigators
It recommends keeping nested navigators to a minimal. Is my method even the right way about going for this UI design? Is there a way to achieve this with only using one navigator?
After some time trying to solve this I found the problem was quite silly of me. navigation.navigate takes the name of the screen to navigate to, but I was giving it the component.
To fix the problem I changed
onPress={() => navigation.navigate(SettingScreen)}
to
onPress={() => navigation.navigate('Settings')}
Add this below your render method!
render () {
const { navigate } = this.props.navigation;
}
And then in the onPress
onPress={() => navigate(SettingScreen)}
Hopefully this helps

Set Screen Title in ReactNative createBottomTabNavigator

I am trying to set the Screen Title of the Navigated Screen by using navigator. But it's not working/ changing and doesn't throw an exception either.
Scenario
I want to load some states from Redux and display nice Header Title in the Navigation Screen when the user clicks on the bottom Tab.
User clicks on second Tab in bottom navigation tab
Load state from Redux
Set Header Title
Expected
I want to set the following text to the Screen title
headerTitle set in Screen
Actual
I declared my BottomTabNavigator as following:
import FilterScreen from '../screens/FilterScreen';
import ItemsListScreen2 from '../screens/ItemsListScreen copy';
const BottomTab = createBottomTabNavigator();
const INITIAL_ROUTE_NAME = 'List';
export default function BottomTabNavigator({ navigation, route }) {
// this code works
navigation.setOptions({ headerTitle: 'header Set in Navigator' });
const [isFilterVisible, toggleFilter] = useState(false);
return (
<>
<BottomTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
tabBarOptions={{
inactiveBackgroundColor: '#42a5f5',
inactiveTintColor: '#ffffff'
}}
>
<BottomTab.Screen
name="List"
component={ItemsListScreen}
options={{
title: 'List',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
<BottomTab.Screen
name="List2"
headerMode="screen"
component={ItemsListScreen2}
screen
options={{
title: 'title List2',
headerTitle: 'my header title',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
....
</BottomTab.Navigator>
</>
);
}
In the above component, I could set the Screen Title by using navigation.setOptions and it works.
So, I tried to do the same operation to set the headerTitle in List2 Screen. But it doesn't have any effect to the screen header. It only changes the label of the tab to "this is a title".
import { StyleSheet, View, Text } from 'react-native';
const ItemsListScreen2 = ({ navigation }) => {
navigation.setOptions({
headerTitle: `headerTitle set in Screen`, // <-- no effect at the screen level
title: "this is a title"
});
return (
<View>
<Text>Screen 2</Text>
</View>
);
}
I tried to do console.log to navigation object passed to the component and I can see it here:
Could you please help me to sort out this issue and suggest me how I could set the title from the screen?
Do I need to create a new function (Eg. getHeaderTitle), load the Redux state and set the title due to the route within BottomTabNavigation component? I want to keep that component clean and I want to load the Redux States only when the user navigates to the screen. I'd like to keep the screen specific codes in its own related screen.

How to implement a shared Search Bar input across stack/tab navigators using react-navigation?

Snapchat's UI is currently setup with a floating SearchBar header that appears to be shared across a few screens/tabs. I'd like to replicate the shared SearchBar header using react-navigation. I currently have a half working solution...
Currently even though I have the headerTitle set on the StackNavigator, it appears that the header is rendering a brand new SearchBar (you can see the slight flicker indicating its rendering) upon navigation to the search results screen.
Here is the setup I have currently for one of the Stacks inside my TabNavigator.
function NetworkStack({ route, navigation }) {
return (
<Network.Navigator
initialRouteName="NetworkEventList"
screenOptions={({ navigation, route }) => ({
headerTitle: () => <Search navigation={navigation} route={route} stackName={"NetworkStack"}/>,
})}>
<Network.Screen
name="NetworkSearchResults"
component={SearchResults}
options={({ navigation, route }) => ({
//headerTitle: () => <Search navigation={navigation} route={route} focused={true} stackName={"NetworkStack"}/>,
headerBackImage: () => <BackButton navigation={navigation} shouldPop={true}/>,
headerBackTitleVisible: false,
gestureEnabled: true
})}/>
<Network.Screen
name="NetworkEventList"
component={NetworkEventList}
options={({ navigation, route }) => ({
headerLeft: () => <ProfileSidebarButton navigation={navigation}/>,
//headerTitle: () => <Search navigation={navigation} focused={false} stackName={"NetworkStack"}/>,
headerRight: () => <CommunityButton navigation={navigation} stackName={"NetworkStack"}/>
})}/>
</Network.Navigator>
)
}
Below is my TabNavigator.
function TabNavigator({ navigation, route }) {
return (
<Tab.Navigator
initialRouteName="NetworkStack"
tabBar={props => <TabBar {...props}/>}>
<Tab.Screen
name="CheckInStack"
component={CheckInStack}/>
<Tab.Screen
name="NetworkStack"
component={NetworkStack}/>
<Tab.Screen
name="MapStack"
component={MapStack}/>
</Tab.Navigator>
);
}
The logic that navigates to the search results component is inside the onFocus listener of the input. Here is the code for that...
const searchBarFocus = () => {
switch(props.stackName) {
case "MapStack":
var searchType = props.searchGoogle ? "AddEstablishment" : "ViewingEstablishments";
props.navigation.navigate('MapSearchResults', {searchType: searchType});
break;
case "NetworkStack":
props.addingMarkers(false);
var searchType = props.searchForPosting ? "ViewingEstablishments" : "ViewingUsers";
let index = null;
let routeState = props.route.state;
if(routeState) index = routeState.index;
if(index !== 1) {
console.log(props.navigation);
props.navigation.navigate('NetworkSearchResults', {searchType: searchType});
}
break;
case "CheckInStack":
props.addingMarkers(false);
props.navigation.navigate('CheckInSearchResults', {searchType: "ViewingUsers"});
break;
}
}
How would I go about configuring my navigation elements so that I have a singular SearchBar element that mounts one time? You can see in the gif I uploaded that the searchbar also loses focus upon navigation, this is also due to the second rendering/mounting of my Search component. Any suggestions would be much appreciated!
I was able to find a solution to my question. Feel free to comment or supply a better answer. I use the react-native-elements Search Bar to create my searching/input element at the header of my navigation stacks. Originally this was a View wrapping the SearchBar component. I changed this to a TouchableOpacity so that I could listen for onPress event. This is the new element I constructed. I kept the original navigation configuration that was supplied in the question.
return (
<TouchableOpacity style={[styles.mainContainer, {width: SEARCH_BAR_WIDTH_UNFOCUSED}]} onPress={() => searchBarPress()}>
<SearchBar
id={"searchBar-"+ props.stackName}
platform="ios"
ref={search => searchRef = search}
value={searchInput}
containerStyle={styles.searchInputContainerWrapper}
inputContainerStyle={styles.searchInputContainer}
inputStyle={{color: styles.inputContainer.color}}
round={true}
autoFocus={props.route.params ? true : false}
pointerEvents={props.route.params ? "auto" : "none"}
cancelButtonTitle="Cancel"
cancelButtonProps={{color: '#707070'}}
onChangeText={updateSearch}
//onFocus={searchBarFocus}
onCancel={() => console.log("Cancel")}
/>
</TouchableOpacity>
)
The key parts of creating the snapchat like search bar header is the autoFocus and pointerEvents properties. The property values needs to be dependent upon which screen the user is currently on.
I think, the good way is to create just a separate component with intention to be a searching component, then when searching happens the results is stored on the global state, as context or redux store
//search.js
const SearchBar = (props) => {...}; //which have access to the global state
//then on your routes
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<SearchBar/>
),
}}
/>
<Stack.Screen
name="Users"
component={UsersScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<SearchBar/>
),
}}
/>
</Stack.Navigator>
);
}

React Navigation (V2 / V3): How to access navigation.state.index in navigationOptions on screen

I'm building a React Native app and for my navigation I use React Navigation (V3). For my UI elements I use React Native Elements.
I have a screen which I reuse in multiple navigators. Sometimes it is at the root of a stack, and sometimes it's pushed on with a stack. I want to programmatically add a headerLeft element to this screen navigationOptions based on its position in the stack, because I either want the default back button to show, or a burger menu icon to show, if the screen is at the root of the stack (because the stack is nested in a drawer).
So I tried the following:
export class ScannerScreen extends Component {
static navigationOptions = ({ navigation }) => ({
headerLeft:
navigation.state.index > 0 ? (
undefined
) : (
<Icon type="ionicon" name="ios-menu" onPress={navigation.toggleDrawer} />
),
title: strings.scannerTitle
});
// ...
The problem is this doesn't work as navigation.state.index is undefined here. How would one do this with React navigation?
Edit:
As per request I added a screenshot of console.log(navigation) (via <Icon />s onPress.)
I found a hacky solution, which is okay but I also dislike it a bit:
const stackConfig: StackNavigatorConfig = {
cardStyle: styles.card,
navigationOptions: {
headerBackTitleStyle: styles.headerBackTitle,
headerStyle: styles.header,
headerTintColor: "black"
}
};
const HomeStack = createStackNavigator(
{ HomeScreen, RestaurantDetailScreen, MenuScreen, ScannerScreen },
{
...stackConfig,
initialRouteName: "HomeScreen",
navigationOptions: ({ navigation }) => ({
headerLeft:
parseFloat(navigation.state.key.slice(-1)) > 1 ? (
undefined
) : (
<Icon
containerStyle={styles.leftIcon}
type="ionicon"
name={getIconName("menu")}
onPress={navigation.toggleDrawer}
/>
),
...stackConfig.navigationOptions
})
}
);
This way it automatically sets it for the whole stack. You can also do this in the respective screen individual navigation options. But this only works, since the automatically assigned key for the screen contains it's index.