How to set browser tab title with route name in React Native - react-native

currently in my app, the browser tab title is just the name of the screen. However I want to set the tab title to {APP_NAME} - {PAGE_NAME}. I am using a <Stack.Navigator/> and I know that you use the screenOptions to give a custom title, but I cannot access the route name.
<Stack.Navigator
initialRouteName={Pages.HOME}
screenOptions={{
title: `MyAppName - ${route.name}`,
header: ({ navigation, back, route }) => (
<Navbar
navigation={navigation}
back={back}
title={route.name}
navigationOptions={[
Pages.HOME,
Pages.MY_MESSAGES,
Pages.PROFILE_AND_ACCOUNT,
Pages.MY_ACCOUNT,
Pages.FAQ,
]}
actions={[logoutAction]}
/>
),
}}
>
As you can see, the header param contains route, but I cannot/don't know how to get this into the title.
Any help would be appreciated, thanks.

You don't need to do this for every screen. You can customize the document title in NavigationContainer
<NavigationContainer
documentTitle={{
formatter: (options, route) =>
`MyAppName - ${options?.title ?? route?.name}`
}}
>
{/* content */}
</NavigationContainer>
https://reactnavigation.org/docs/navigation-container/#documenttitle
Though also if you need the route name in options as mentioned in docs:
<Stack.Navigator
screenOptions={({ route }) => ({
title: route.name,
})}
>
https://reactnavigation.org/docs/screen-options/#screenoptions-prop-on-the-navigator

Related

React Native - Bottom Tab Navigator - error - Passing an inline function will cause the component state to be lost on re-render?

I have a stack navigator which contains a Bottom tab navigator.
The first tab of the bottom tab navigator further contains a TopTab Navigator .
This is being done to display a Top Tab as well as a Bottom tab.
Each of the other bottom tab screens open a new screen .
Here is the code : ( stack navigator containing Bottom Tab Navigator )
const StackNav = createNativeStackNavigator();
function Main() {
return (
<NavigationContainer>
<StackNav.Navigator>
<StackNav.Screen
name="BottomTab"
component={BottomTabScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerBlurEffect: 'dark',
}}
/>
</StackNav.Navigator>
</NavigationContainer>
);
}
The Bottom Tab Navigator is below ( first tab contains an embedded Top Tab Navigator TopTabScreen to display header , rest of the tabs show tabs on the bottom )
const BottomTabScreen = () => {
return (
<BottomNav.Navigator barStyle={{backgroundColor: '#febe00'}}>
<BottomNav.Screen
name="Home"
component={TopTabScreen}
options={{
tabBarIcon: ({color, size}) => (
<MaterialCommunityIcons name="home" color={color} size={26} />
),
labelStyle: {textTransform: 'none'},
upperCaseLabel: false,
}}
/>
<BottomNav.Screen
name="MyInitiatives"
component={InitiativesScreen}
options={{
tabBarIcon: ({color, size}) => (
<IonIcons name="rocket-outline" color={color} size={26} />
),
}}
/>
<BottomNav.Screen
name="Contact-Whatsapp"
component={() => null}
listeners={{
tabPress: (e) => {
e.preventDefault();
console.log("tabPress tabTwo");
let link = 'https://wa.me/xxx';
Linking.openURL(link);
},
}}
options={{
tabBarIcon: ({color,size}) => (
<FontAwesome name="whatsapp" color={color} size={26} />
),
}}
/>
</BottomNav.Navigator>
);
};
So on bottom tab - three tabs will show :
Home - tab which shows the embedded header
MyInitiatives - shows a separate view when clicked
Contact-Whatsapp - this is a tab which I am trying to show which when clicked should open whats
app and NOT a new screen , so on clicking the tab - I dont want any
'component' to render , rather simply whats app to open
Note - whatsapp is opening but am getting this warning :
WARN Looks like you're passing an inline function for 'component' prop for the screen
'Whatsapp'
(e.g. component={() => <SomeComponent />}). Passing an inline function will cause the
component state to be lost on re-render and cause perf issues since it's re-created every
render. You can pass the function as children to 'Screen' instead to achieve the desired
behaviour.
So questions are :
#1 how do I get around this issue ?
how do I ensure that on clicking on bottom tab - it should open the link ( in this case - whatsapp ) rather than try and open a component / view ?
The reason for the warning is that you are passing a function to the component prop rather than the name of the function. You can put any component name in there as with the e.preventDefault() it will not be opened. You can just create a small dummy component and pass in the name. Even component={View} should do the job.

How to update navigation header from screen?

react-native version 0.62
I am making a quiz app and on every new question(a new quiz screen, QuestionScreen.js), I want to update a label in the navigation bar which is declared in App.js
In App.js, the problem here is I don't know how to pass the state questionIndex to HomeScreen or QuestionScreen, normally I thought this would be using props like <Question questionIndex={questionIndex}>. I tried to pass them in options but that doesn't seem to work...
const App = () => {
const [questionIndex, setQuestionIndex] = useState(0);
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'
>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
title:''
}}
/>
<Stack.Screen
name='QuestionScreen'
component={QuestionScreen}
options={({ navigation }) => ({
headerBackTitleVisible: false,
headerRight: ()=>(<HeaderRight pageIndex={questionIndex}/>),
headerRightContainerStyle: {
alignItems: "flex-end",
paddingRight: 25
},
})}
/>
function HeaderRight(props) {
return (
<Text>{props.pageIndex}/{questions.length-1}</Text>
);
}
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container} >
<ScrollView>
<TouchableOpacity onPress={()=>
//here I'm unable to retrieve the questionIndex props.
//tried navigation.getParams("questionIndex") or route.params
navigation.navigate('QuestionScreen', {
pageIndex: 0
})
}>
In QuestionScreen.js
function QuestionScreen({route, navigation, options}) {
const { pageIndex } = route.params;
[Full code here on github]https://github.com/liaoxsong/expofirst
I am a newbie to react-native and I come from native mobile development, this Stack.Navigator api looks weird, can't I just manage the navigation header from the screen component itself? That would make things easier.
it is difficult to customize the default header given by react navigation, what you can do is disabling the header and creating your own custom header.
disable header by giving prop headerMode={"none"} in createStackNavigator and have your own custom header on top,
to disable header docs : https://reactnavigation.org/docs/stack-navigator/#headermode

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.

React Native StackNavigator: Passing props from a screen to the header of the next screen

I have a React Native StackNavigator like so:
const AppStack = () => {
return (
<NavigationContainer theme={{ colors: { background: "white" }}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen name="Master" component={ Master } />
<Stack.Screen
name="Details"
component={ Details }
options={{ headerTitle: props => <Header {...props} /> }} // <-- how can I pass props from Master to the Header here
/>
</Stack.Navigator>
</NavigationContainer>
)
}
The navigation works fine. When I'm on Master, if I press a TouchableOpacity, it brings up Details, with the header component Header.
However, what I want is to pass props from Master to the Header component in Details.
Something like this:
{/* What I want is that onPress, I want to pass someones_name and someones_photo_url to
the Details' Header component */ }
const Master = () => {
const someones_name = "Steve";
const someones_photo_url = "http://somephoto.com/001.jpg";
return (
<TouchableOpacity onPress={() => navigation.navigate("Details")}>
<Text>{ someones_name }</Text>
<Image source={{ uri: someones_photo_url }}>
</TouchableOpacity>
)
}
Is this possible?
You can pass values to other screens when navigating by doing the following:
Assume I am on screen "Feed" and click on someones name thus I want to go to screen "Profile" where it will show that user name that I clicked on.
In screen Feed
props.navigation.navigate('Profile, { userName: 'Shane' })`
In screen Profile I can grab that value passed in via navigate with:
const { userName } = props.route.params
To set this up inside a header assuming your screen options are outside the screen component and cannot access the props. Look into using setOptions to configure your header title dynamically.
props.navigation.setOptions({
headerTitle: ...
})

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>
);
}