Programmatically update react navigation header title in functional component with hooks - react-native

question regarding react navigation setParams(). I asked on Reactiflux, but no one responded. I'm trying to set a title programmatically in a function component.
From another Stack Overflow thread, updating the static title retroactively, like this, works:
const Comp = props => { ... };
Comp.navigationOptions = ({ navigation }) => ({
title: 'Static Title'
});
But I need to access the component state from within the component, this does not work:
const Comp =({ navigation }) => {
const [title, setTitle] = useState('');
useEffect(() => {
navigation.setParams({ title });
}, [title]);
return ( ... );
}
If setParams() is the wrong way to do it, please enlighten me
EDIT: To add to this, when I console.log(navigation) I can see that it is changing navigation.state.params.title to the correct string, however it doesn't show up as the title.

You need to get the title param and apply it to the title:
Comp.navigationOptions = ({ navigation }) => ({
title: navigation.getParam('title', /* your default title */)
});

First you need to implement your navigation option as a function and this function needs navigation parameter as shown example in following lines :
const cameraScreenNavigator = {
'/auth/controlledCameraScreen': {
screen: ControlledCameraScreen,
navigationOptions: ({navigation}) => ({
title: navigation?.getParam('title', "Başvuru Adı"),
headerStyle: style.navigationHeaderBlue,
headerTitleStyle: {
fontWeight: 'bold',
color: COLORS.primaryText
},
}),
}
}
Then you can call setParams function in your component. This function acting as setState but react-nativagion suggests this for
setParams "setParams/setOptions etc. should only be called in useEffect/useLayoutEffect/componentDidMount/componentDidUpdate etc. Not during render or in constructor."
props.navigation.setParams({title: "New Title"})
resource : https://reactnavigation.org/docs/stack-navigator/

Related

React navigation undefined params

I'm trying to pass params into a new screen, and implemented it like mentioned here.
I have the following TouchableOpacity button.
<TouchableOpacity
onPress={() => {
this.props.navigation.navigate('SomeScreen', {
title: 'Title',
subTitle: 'Subtitle',
});
}}
>
On the other page (let's call it Somescreen), I have the following:
render() {
const { navigation } = this.props;
const title = navigation.getParam('title');
}
But title above is undefined:
{ params: undefined, routeName: "Somescreen", key: "id-xx" }
My rootStack:
const RootStack = createStackNavigator({
SomescreenA: { screen: SomescreenA },
SomescreenB: { screen: SomescreenB },
}, { headerMode: 'none' });
Why are my params undefined in a new screen?
If you face a situation where your target screen get undefined params, probably you have a nested navigation stack.
Here you have to pass params to the navigate method in this way:
navigation.navigate('Root', {
screen: 'Settings',
params: { user: 'jane' },
});
For more information read this page in the official docs:
https://reactnavigation.org/docs/nesting-navigators/#navigating-to-a-screen-in-a-nested-navigator
In my specific case, I was calling a nested navigator, so I had to manage how send those params to their specific screen, so I did this:
Send params this way...the regular way:
navigation.navigate(
'OrderNavigator',
{itemSelected},
);
Then, from navigator stack I did this:
const OrderNavigator = ({route: {params}}) => {
return (
<Stack.Navigator initialRouteName="Order">
<Stack.Screen name="Order" component={Order} options={{headerShown: false}} initialParams={params} />
</Stack.Navigator>
);
};
And that's it. Then from the screen I got them like this:
const Order = ({route}) => {
const {itemSelected} = route.params;
const {first_name, last_name} = itemSelected;
return (...)
}
I've, unfortunately, encountered cases where navigate(route, params, ...) wouldn't pass the params object, just like you did.
As a workaround, I use the other variant - navigate({routeName, params, action, key}) that you can find here. It always works.
The accepted answer workaround did not work for me, so apparently if you use children to render your component (in screen options) and pass route as a prop, it works
if you are on react navigation v6^ use the useRoute hook to access the params object
const route = useRoute();
useRoute is a hook that gives access to the route object. It's useful when you cannot pass the route prop into the component directly, or don't want to pass it in case of a deeply nested child.
below is an implementation of this
import { useNavigation, useRoute } from '#react-navigation/native';
import { Pressable, Text } from 'react-native';
function Screen1() {
const navigation = useNavigation();
return (
<Pressable
onPress={() => {
navigation.navigate('Screen2', { caption: 'hey' });
}}
>
<Text> Go to Screen 2 </Text>
</Pressable>
);
}
function Screen2() {
const route = useRoute();
return <Text>{route.params.caption}</Text>;
}

Change Bottom Tab Bar based on state in React Navigation with navigationOptions

I want to change the bottom tabs on the screen based on what features are enabled. This feature list is populated via a login API call.
Currently I have the following:
const TabRouteConfig = {
Home: DashboardScreen,
FeatureA: FeatureA,
FeatureZ: FeatureZ,
};
const TabNavigatorConfig = {
initialRouteName: 'Home',
order: [
'Home',
'Feature A',
],
tabBarPosition: 'bottom',
lazy: true,
};
const TabNavigator1 = createBottomTabNavigator(
TabRouteConfig,
TabNavigatorConfig,
);
// Set the tab header title from selected route
// https://reactnavigation.org/docs/en/navigation-options-resolution.html#a-stack-contains-a-tab-navigator-and-you-want-to-set-the-title-on-the-stack-header
TabNavigator1.navigationOptions = ({ navigation }) => {
const { index, routes } = navigation.state;
const { routeName } = routes[index];
return {
headerTitle: routeName,
};
};
const TabNavigator2 = createBottomTabNavigator(
TabRouteConfig,
{
...TabNavigatorConfig,
order: [
'Home',
'Feature Z',
]
);
TabNavigator2.navigationOptions = TabNavigator1.navigationOptions;
const Stack = createStackNavigator({
Main: {
screen: props => (props.screenProps.hasFeature ?
<TabNavigator1 /> : <TabNavigator2 />
)
},
})
const WrappedStack = props => (
<View style={styles.container}>
<Stack
screenProps={{ hasFeature: props.hasFeature }}
/>
</View>
);
const mapStateToProps = (state, props) => {
return {
...props,
hasFeature: state.hasFeature,
};
};
export default connect(mapStateToProps, null)(WrappedStack);
This mostly works - it dynamically switches between TabNavigator1 and TabNavigator2 based on hasFeature, but it no longer honors the navigationOptions placed on the TabNavigators and the headerTitle is not set.
Is there a better way to do this?
It's an antipattern to render more than one navigator simultaneously as the navigation states of those navigators will be completely separated, and you will not be able to navigate to one from another.
You can use tabBarComponent option to achieve what you want. Hope you can get the idea from below example:
import { createBottomTabNavigator, BottomTabBar } from 'react-navigation-tabs';
const TabNavigator = createBottomTabNavigator(
TabRouteConfig,
{
tabBarComponent: ({ navigation, ...rest }) => {
const filteredTabNavigatorRoutes = navigation.state.routes.filter(route => isRouteAllowed(route));
return (
<BottomTabBar
{...rest}
navigation={{
...navigation,
state: { ...navigation.state, routes: filteredTabNavigatorRoutes },
}}
/>
);
},
},
);
NOTES:
You don't have to install react-navigation-tabs separately. It is automatically installed with react-navigation 2.0.0+.
isRouteAllowed is the function which returns true or false based on whether to show that route or not. Make sure to only check the top level routes in that object.
TabRouteConfig should contain all possible tabs, and this logic only hides the route from the TabBar visually. So, you can still programmatically navigate to all routes. Therefore, you might need additional logic in each of those screens to decide whether to render them based on hasFeature.

Send props on navigation goBack

Basically I have three screens, the first is a stack navigator:
const stackNav = createStackNavigator({
Main: {
screen: MainScreen,
navigationOptions:({navigation}) => ({
header: null,
})
},
Detail: {
screen: DetailScreen,
navigationOptions: (props) => ({
title: "Detail",
})
}
})
The second one I have a button to go to the Detail screen:
<TouchableOpacity onPress={() => this.props.navigation.navigate("Detail", {name: l.name, subtitle: l.subtitle})}>
The last one is just information, I would like to click a button and execute:
this.props.navigation.goBack(), but sending props to the second screen (MainScreen), a setState and a integer id, how can I do that?
--EDIT WITH PARAMS--
I have this function in MainScreen:
handleOrdem(texto) {
console.log('texto: '+texto)
this.setState({
param: global.ordemAtiva,
ordemAtiva: !this.state.ordemAtiva
});
}
//The onPress code:
onPress={() => this.props.navigation.navigate("Detail", {name: l.name, subtitle: l.subtitle, ordemFunc: () => this.handleOrdem()})}>
and this is how I call it in Detail.screen:
execBack(param){
console.log('param: '+param);
this.props.navigation.state.params.ordemFunc(param);
this.props.navigation.goBack();
}
//Button to do it
onPress={() => this.execBack('test')}
Create a Method in parent screen
returnData(){
PERDROM_EVENT_WITH_RECEIVED_DATA
}
Then bind this method returnData with navigation code while executing navigation code
this.props.navigation.navigate("Detail", {name: l.name, subtitle: l.subtitle , returnData: this.returnData.bind(this)})}
In child Component call returnData method before call of goBack()
this.props.navigation.state.params.returnData(RETURN_DATA_YOU_WANT);
this.props.navigation.goBack();
Handling return data
Suppose you want two parameters back then add two parms in returnData() method
For example we took first param is boolean and second param String
returnData(flag,id){
USE THIS `flag` and `id` to update state or method call or
What ever you wanted too.
}
And inside Child component pass these two param
this.props.navigation.state.params.returnData(VALUE_OF `flag`, Value of `id`);
FOR EDIT WITH PARAMS
replace your code of navigation with this line
this.props.navigation.navigate("Detail", {name: l.name, subtitle: l.subtitle, ordemFunc: this.handleOrdem.bind(this)})>
You have to bind method not to call with arrow function
So the problem is
ordemFunc: () => this.handleOrdem()
Replace this line with
ordemFunc: this.handleOrdem.bind(this)
I came across this exact same issue and the problem is actually quite simple. We will utilise a callback for passing the params when we trigger goBack()
For example lets say I have two Views: ViewA and ViewB.
For which I will do as follows:
import useNavigation hook for setting up navigation.
When navigating to the next screen pass a callback ie: function with the specified paramaters for the values you would like to pass back to ViewA.
In ViewB use the route prop to get access to the params. In here you will find your callback.
Use your callback and pass in the correct arguments to your callback.
Call the navigation.goBack() to return to ViewA
In ViewA you will now have access to your value in your callback.
import React from "react";
import { View, Button } from "react-native";
import { useNavigation } from "#react-navigation/native";
const ViewA = () => {
const navigation = useNavigation();
return (
<View>
<Button
onPress={() =>
navigation.navigate("ViewB", {
handleItem: (item) => console.log(item), // will log out "Your Item"
})
}
/>
</View>
);
};
const ViewB = ({ route }) => {
const navigation = useNavigation;
return (
<View>
<Button
onPress={() => {
route.params.handleItem("Your Item");
navigation.goBack();
}}
/>
</View>
);
};
If you are using react-navigation v2 you no need to use navigation.goBack() to go back to Main screen
this.props.navigation.navigate('Main', { myParam: value }) will declaratively handle the navigation back (with same transition) for you
Maybe you can save the data in the global state and call it again when you goBack
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log("its focus");
//call the new data and update state here
});
return unsubscribe;
}, [navigation]);

how to use tabBarOnPress in tabnavigator react native

i am stuck in big problem that is i wants onPress event when i clicked on tab.
my code is here:-
static navigationOptions = ({navigation, screenProps}) => {
const params = navigation.state.params || {};
console.log("Params:-",params);
return {
title:Strings.title_dashboard,
headerStyle:{ backgroundColor: Colors.header_blue},
headerTitleStyle:HeaderStyle.titleCenter,
tabBarOnPress: (tab, jumpToIndex) => {
console.log("Tab Click",tab);
jumpToIndex(tab.index);
navigation.state.params.onFocus()
},
headerRight:<TouchableOpacity onPress={()=>Alert.alert(Strings.avanza,Strings.under_imple)}><View><Image source={{uri: "filter_icon"}} style={{height: 18, width: 18,marginRight:10,}} /></View></TouchableOpacity>,
}
}
at here i set the Params like this in componentDidMount:
this.props.navigation.setParams({
handleGrid: this.callServer.bind(this)
})
getting an error here not able to get this click event.
Help me please.
Thank you.
This is my approach. It works for the version 5.x.x of react-navigation:
const BottomTab = createBottomTabNavigator();
const Tabs = props => (
<BottomTab.Navigator
initialRouteName="Foo"
...
<BottomTab.Screen
name="Foo"
component={Foo}
initialParams={props.route.params}
listeners={{
tabPress: e => {
// e.preventDefault(); // Use this to navigate somewhere else
console.log("Foo tab bar button pressed")
},
}}
/>
</BottomTab.Navigator>
);
Read more about listeners.
For version 3.x.x and I hope for the 4th as well please use this one.
let Tabs = createBottomTabNavigator(
{
FooTab: Foo,
},
{
initialRouteName: "FooTab",
defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
console.log('onPress:', navigation.state.routeName);
defaultHandler()
},
}),
}
);
For version 2.x.x please use navigationOptions instead of the defaultNavigationOptions.
This is working for me,
static navigationOptions = ({ navigation }) => {
return {
tabBarOnPress: ({previousScene, scene, jumpToIndex}) => {
const { route, index, focused} = scene;
if(focused){
navigation.state.params.scrollToTop()
}
jumpToIndex(0)
}
}
};
I used stack navigator
const Stack = createStackNavigator({
ScreenA: {
screen:ScreenA ,
navigationOptions: () => ({
header: null
}),
},
ScreenB: {
screen:ScreenB ,
navigationOptions: () => ({
header: null
}),
},
});
//Added tabBarOnPress
https://reactnavigation.org/docs/en/stack-actions.html
the popToTop action takes you back to the first screen in the stack, dismissing all the others. It's functionally identical to StackActions.pop({n: currentIndex}).
import { StackActions } from 'react-navigation';
let Tabs = createBottomTabNavigator(
{
FooTab: Foo,
},
{
initialRouteName: "FooTab",
defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
// to navigate to the top of stack whenever tab changes
navigation.dispatch(StackActions.popToTop());
defaultHandler();
]},
}),
}
);

react-navigation can't insert value of my axios in the title; "undefined is not an object"

I'm trying to put the value got in my axios inside the title of the navigation but I still have the same error message.
this is my code :
fetchWeather () {
axios.get(`http://api.openweathermap.org/data/2.5/forecast/daily?q=${this.state.city}&mode=json&units=metric&cnt=10&APPID=94c6cf0868fa5cb930a5e2d71baf0dbf`)
.then((response) => {
this.setState({report: response.data});
})
}
static navigationOptions = ({navigation}) => {
return {
title: `Météo de ${this.state.report.city.country}`,
tabBarIcon: () => {
return <Image source={require('./images/avatar.png')} style={{width: 20, height: 20}}/>
}
}
}
Someone can help me ?
Since navigationOptions is a static function, it is attached to the class definition and not the instance. This means that it cannot use this to reach data from a specific instance, which is why you're getting undefined.
Instead, you could either set the data to somewhere more global, such as a Redux store, and read it from there, or set the data as a parameter to the navigation state using setParams:
...
.then(response => this.navigation.setParams({report: response.data});
And then in your navigationOptions:
title: `Météo de ${navigation.state.params.report.city.country}`