React-navigation crash when going back from a nested navigation - reactjs

I'm using React-navigation 6 on my React native application
I have two StackNavigator in a BottomStackNavigator with those screens and params on each stack :
export type BottomNavigationStack = {
Planner: undefined,
Profile: undefined,
}
export type PlannerNavigationStack = {
PlannerHome: undefined,
PlannerDetails: {
detail: { ... }
},
Profile: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to ProfileNavigationStack from PlannerNavigationStack
}
export type PlannerNavDetailProps = StackScreenProps<PlannerNavigationStack, 'PlannerDetails'>
export type ProfileNavigationStack = {
ProfileHome: undefined,
ProfileAddThings: {
service: string
from: string,
},
Planner: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to PlannerNavigationStack from ProfileNavigationStack
}
export type ProfileNavAddThingsProps = StackScreenProps<ProfileNavigationStack, 'ProfileAddThings'>
So i'm trying to navigate from PlannerStack to ProfileStack, when I'm in ProfileStack, I want to go back to PlannerStack.
I got no problem going to ProfileStack from PlannerStack like this :
navigation.navigate('Profile', {
screen: 'ProfileAddThings',
params: {
service: 'myservice',
from: 'myfrom',
},
initial: false,
})
So at that moment I'm on ProfileStack on screen ProfileAddThings.
if I read the documentation, the PlannerStack keep it's history, and if I click on the bottomTab button of Planner I can see the page is still PlannerDetails.
But, when I click on the back button of the screen ProfileAddThings, I'm going back to ProfileHome.
I tried to overide the backButton action of the screen ProfileAddThings with that code :
navigation.navigate('Planner', { screen: 'PlannerDetails' })
But I got the error : undefined is not an object (evaluating 'route.params.detail')
Detail is a parameter of PlannerDetails screen.
I really don't understand why this parameter is undefined because de PlannerStack history is still present.
Someone has already gone back from a nested navigation in react native ?
Thanks

You need to get the parent navigator.
You can do this by using the getParent() method from the current screen.
Will be something like this:
let parent = navigation.getParent(); // This will assing PlannerStack to the parent variable.
then you should call navigation method with the Route name
parent.navigate('PlannerDetails');
The route history, register the hole component from the screen (in your case PlannerStack) and in your PlannerStack the innitialRoute probably is ProfileHome, thats the reason you get there when using the back button.

Related

How to pass parameters between two screens in react native?

Please, do not hurry to close this question as duplicate. I have read the official documentation here and so fa I have managed to pass successfully parameters between screens. There is som problem with my current case, and the solution is not obvious for me.
I have two screens. The first is called "Feed" and it belongs to bottom tab navigator. From the "Feed" screen I want to be able to navigate to "Comments" screen which is part of stack navigator.
I am able to successfully navigate between both screens, however, for some reason I am not able to pass any parameters when I navigate from "Feed" to "Comments".
Part of my "Feed" screen where I press "comments" icon:
<TouchableOpacity
onPress={() => {
console.log("passing param id: ", id);
navigation.navigate("Comments", { id: id });
}}
>
<FontAwesome name="comment-o" size={30} color="#0047AB" />
</TouchableOpacity>
What I see on the console:
passing param id: some_id_whic_does_not_matter
I manage to successfully navigate to my "Comments" screen. Part of my "Comments" screen:
const CommentsScreen = (props) => {
console.log("Comments Screen: ");
console.log("comments: ", props);
What I see on the console for the route object:
route": Object {
"key": "Comments-some-key",
"name": "Comments",
"params": undefined,
}
Can you tell me what I am doing wrong?
Where I am currently located on NetworkNavigator tab, and from there I want to navigate to the Comments screen.
I still dont see where the issue is?
I think we access params via route I mean
const CommentsScreen =({route})=>{
const {id} = route.params;
console.log(id);
...}
You can access your passed params via following
const CommentsScreen = (props) => {
console.log("Comments Screen: ");
console.log("comments: ", props.route.params.id);
props.route.params
It contains the object you passed in
navigation.navigation('screenname', {
...this object...
});
you can extract the data by using the same keys.
Example
navigation.navigation('screenname', {
id: '1',
name: 'Adam',
age: 12
});
//Then you can access via
console.log("id: ", props.route.params.id);
console.log("name: ", props.route.params.name);
console.log("age: ", props.route.params.age);
I found the answer to my question here: https://reactnavigation.org/docs/5.x/nesting-navigators#passing-params-to-a-screen-in-a-nested-navigator
So in my case:
navigation.navigate("Root Navigator", {
screen: "Comments",
params: { id: id },
});

Passing function as a param in react-navigation 5

NOTE: This query is for react-navigation 5.
In react navigation 4 we could pass a function as a param while navigating but in react navigation 5, it throws a warning about serializing params.
Basically, what I am trying to do is, navigate to a child screen from parent screen, get a new value and update the state of the parent screen.
Following is the way I am currently implementing:
Parent Screen
_onSelectCountry = (data) => {
this.setState(data);
};
.
.
.
<TouchableOpacity
style={ styles.countrySelector }
activeOpacity={ 0.7 }
onPress={ () => Navigation.navigate("CountrySelect",
{
onSelect: this._onSelectCountry,
countryCode: this.state.country_code,
})
}
>
.
.
.
</TouchableOpacity>
Child Screen
_onPress = (country, country_code, calling_code) => {
const { navigation, route } = this.props;
navigation.goBack();
route.params.onSelect({
country_name: country,
country_code: country_code,
calling_code: calling_code
});
};
Passing a callback through react native navigation params is not recommended, this may cause the state to freeze (to not to update correctly). The better solution here would be using an EventEmitter, so the callback stays in the Screen1 and is called whenever the Screen2 emits an event.
Screen 1 code :
import {DeviceEventEmitter} from "react-native"
DeviceEventEmitter.addListener("event.testEvent", (eventData) =>
callbackYouWantedToPass(eventData)));
Screen 2 code:
import {DeviceEventEmitter} from "react-native"
DeviceEventEmitter.emit("event.testEvent", {eventData});
useEffect(() => {
return () => {
DeviceEventEmitter.removeAllListeners("event. testEvent")
};
}, []);
Instead of passing the onSelect function in params, you can use navigate to pass data back to the previous screen:
// `CountrySelect` screen
_onPress = (country, country_code, calling_code) => {
const { navigation, route } = this.props;
navigation.navigate('NameOfThePreviousScreen', {
selection: {
country_name: country,
country_code: country_code,
calling_code: calling_code
}
});
};
Then, you can handle this in your first screen (in componentDidUpdate or useEffect):
componentDidUpdate(prevProps) {
if (prevProps.route.params?.selection !== this.props.route.params?.selection) {
const result = this.props.route.params?.selection;
this._onSelectCountry(result);
}
}
There is a case when you have to pass a function as a param to a screen.
For example, you have a second (independent) NavigationContainer that is rendered inside a Modal, and you have to hide (dismiss) the Modal component when you press Done inside a certain screen.
The only solution I see for the moment is to put everything inside a Context.Provider then use Context.Consumer in the screen to call the instance method hide() of Modal.

react-navigation: Can the screens have different names then what is passed in the routeConfig?

I have a situation where I do not intend for the keys I pass in the route config to be presented to the user:
const routes = {
'tab/Search': Search,
'tab/Scan': Scan,
'tab/Me': Me
};
const Nav1 = createBottomTabNavigator(routes, ...);
const routes = {
'stack/Camera': Camera,
'stack/Home': Home,
'stack/ItemDetails': ItemDetails,
'stack/SearchItems': SearchItems
};
const Nav2 = createStackNavigator(routes, ...);
I need to use these keys as identifiers for the screens in code so I can tell whether we're coming from a stack navigator screen or a tab navigator screen by checking routeName.startsWith('tab/'). Is it possible to change the display name in the UI? Thanks.
In your actual screen component:
export class Search extends Component {
static navigationOptions = {
title: 'Search'
}
...etc
}
You can also use tabBarLabel in that object for screens within a tab bar.

react-navigation deep linking with multiple paths

Is there any way to configure react-navigation so that single screen can handle multiple links?
Each screen in StackNavigator can have an optional property path which enabled deep links, StackNavigator also accepts paths option that lets u override paths per specific screen but it's still one-to-one mapping.
Is there a way to declare unlimited amount of paths that should be handled by single screen?
You can use variables for unlimited number of paths like its shown in StackNavigator docs
Example from docs
StackNavigator({
// For each screen that you can navigate to, create a new entry like this:
Profile: {
// `ProfileScreen` is a React component that will be the main content of the screen.
screen: ProfileScreen,
// When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.
// Optional: When deep linking or using react-navigation in a web app, this path is used:
path: 'people/:name',
// The action and route params are extracted from the path.
// Optional: Override the `navigationOptions` for the screen
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile'`,
}),
},
...MyOtherRoutes,
});
Update
You can create a custom route handler for more detailed control over paths shown here.
Example from docs
import { NavigationActions } from 'react-navigation'
const MyApp = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
}, {
initialRouteName: 'Home',
})
const previousGetActionForPathAndParams = MyApp.router.getActionForPathAndParams;
Object.assign(MyApp.router, {
getActionForPathAndParams(path, params) {
if (
path === 'my/custom/path' &&
params.magic === 'yes'
) {
// returns a profile navigate action for /my/custom/path?magic=yes
return NavigationActions.navigate({
routeName: 'Profile',
action: NavigationActions.navigate({
// This child action will get passed to the child router
// ProfileScreen.router.getStateForAction to get the child
// navigation state.
routeName: 'Friends',
}),
});
}
return previousGetActionForPathAndParams(path, params);
},
});

Navigate to parent of parent stack

I have this scenario where i have a StackNavigator nested in a TabNavigator nested in another StackNavigator.
const TabOneStack = StackNavigator({
ScreenA: { screen: ScreenA },
ScreenB: { screen: ScreenB }
});
const MainTabs = TabNavigator({
TabOne: { screen: TabOneStack },
TabTwo: { screen: TabTwoStack }
});
const Root = StackNavigator({
HomeScreen: { screen: HomeScreen },
MainTabs: { screen: MainTabs }
});
Everything works but i cant figure out how to navigate for example from ScreenA back to the Home screen in the root StackNavigator.
After the HomeScreen the User navigates directly to ScreenA.
The back button in the header in ScreenA works fine and brings me back to Home but need a way to have a button that brings me back to the HomeScreen.
this.props.navigation.goBack() does not work unfortunately.
i also tried
const backAction = NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({ routeName: 'HomeScreen'})
]
});
this.props.navigation.dispatch(backAction));
but i get:
There is no route defined for key HomeScreen. Must be one of: 'ScreenA', 'ScreenB'.
What is the correct way to do this?
To traverse from child StackNavigator back to parent StackNavigator, do:
class ScreenA extends Component {
render() {
return (<Button onPress={() => {
this.props.navigation.dispatch({type: 'Navigation/BACK'});
}} title='ScreenA. back to Home' />);
}
}
this.props.navigation.dispatch() with 'Navigation/BACK' works exactly the same as the top-most back button.
It differs from goBack() by apply parent-child stack traversing, while goBack() does not.
Result:
And note #Jigar's answer is also correct, it's just a shorthand notation of mine. The key is to pass null argument into goBack(). It won't work without that.
this.props.navigation.goBack(null);
In the new react native version we use 'StackActions' for this case:
StackActions reference
the 'key' parameter in this case is the key to solve this problem. Usually we manage the stack like this:
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'ScreenA' })],
});
this.props.navigation.dispatch(resetAction);
but StackActions object has another option and its: 'key' - its an optional string or null - If set, the navigator with the given key will reset. If null, the root navigator will reset.
So in this case, to reset back to root and navigate to some screen inside it -> in your case from 'ScreenA' to 'HomeScreen' - you should write:
this.props.navigation.dispatch(
StackActions.reset({
index: 0,
key:null,
actions: [
NavigationActions.navigate({
routeName: 'HomeScreen'
})
]
})
);
Notice, in this new version we no longer use 'NavigationActions' to reset the stack, only 'StackActions'.
use this
this.props.navigation.goBack(null);
In v6 you can use something like this:
navigation.getParent()?.navigate('Home')
For me, it's should working fine by using this:
Change this:
this.props.navigation.dispatch(backAction));
to
this.navigator.dispatch(backAction);
Also in your render
render() {
return (
<Root
ref={(nav) => {
this.navigator = nav;
}}
/>
);
}

Resources