this.props.navigation vs NavigationActions in react-navigation - react-native

NavigationActions supported below actions but I can't understand what is the usage of each one when we have access to helper functions with this.props.navigation.
navigate
reset
back
init
so why we have to import NavigationActions?
Thanks in Advance.

They all have lots of usage but let me give you couple of examples.
Example 1 setParams
Lets say you have a StackNavigator with 2 screens.
const Navigator = StackNavigator({
Home: { screen: Home },
Items: { screen: Items },
Profile: { screen: Profile }
});
Now lets say you navigated to Items screen and then to Profile screen and you are listing Items according to this.props.navigation.state.params.type. Lets say this type can be set through Profile screen.
When user changed the setting of type you need to re-render Items screen. But when you do this.props.navigation.goBack() from Profile screen, Items screen will not re-render unless you do some change on props or state.
At this position setParams action required. Although you can use this.props.navigation.setParams on Profile screen it will set params for the current screen.
NavigationActions lets you set params for other screens with key property of those screens.
Example 2 reset and navigate
In your app at some point (logout, refresh, notification etc.) you might want to reset the navigation stack and then recreate the stack with the desired position.
When the user receives a notification it is the expected behavior to redirect to the related post/article/message and when user wants to goBack user should redirect to the correct screen. At this point you can simulate the users navigation with combination of reset and navigate actions.
There are a lot more use cases of these actions. But at the end, if you don't need them you don't have to import them. At some point if you need them you will understand how to use them more.

Besides what #bennygenel said, NavigationActions also good to navigate in nested stacks.
For example navigating from a one screen to a screen in different stack.
Example:
index.js
const StackOpportunityNavigator = createStackNavigator(
{
MainOpportunity: OpportunityScreen,
SecondScreen: SecondScreen
},
{
initialRouteName: 'MainOpportunity',
headerMode: 'none',
transitionConfig: () => fromLeft(600)
}
);
const DrawerNavigationOptions = createDrawerNavigator(
{
OpportunityStack: { screen: StackOpportunityNavigator },
History: HistoryScreen
},
{
contentComponent: props => <SideBar {...props} />,
drawerPosition: 'right',
headerMode: 'none',
transitionConfig: () => fromLeft(600)
}
);
const LoginNavigator = createStackNavigator(
{
LoadingScreen: LoadingScreen,
LoginScreen: LoginScreen,
DrawerNavigator: DrawerNavigationOptions
},
{
transitionConfig: () => fromLeft(600),
navigationOptions: {
header: props => <HeaderBar {...props} />
}
}
);
export default LoginNavigator;
Now i can from a LoginScreen.js navigate to MainScreen(OpportunityScreen.js), by writing the whole nested navigation path that needed to be done from one screen to another:
const navigateToMainScreen = NavigationActions.navigate({
routeName: 'DrawerNavigator',
action: NavigationActions.navigate({
routeName: 'OpportunityStack',
action: NavigationActions.navigate({
routeName: 'MainOpportunity'
})
})
});
_this.props.navigation.dispatch(navigateToMainScreen);
Hope it helps :)

Every one of those helper functions map into navigation.dispatch(action), with a different action object from NavigationActions.
There are a few instances where you still need to use them, for example, when using navigate, you can change the navigation hierarchy on more than one level by specifying a sub action to happen on the navigator that you just routed to.
this.props.navigation.navigate("some-root", {param: "value"}, someOtherAction)
Where someOtherAction can be any action object.
You can find more information here.

Related

Unmount or re-render screen in drawer navigator

I just added drawer navigator recently and wrapped my screens in createDrawerNavigator()
These are my current routes:
Home.js
Post.js
Settings.js
When a user navigates from Home to Post I pass a param that has the post data.
onPress={() => this.props.navigation.navigate('Post', {postData: postData})}
When ever the user goes back to Home from Post, then post will be unmounted. and mounted back again with fresh data when another post is clicked.
My issue is that with implementing the drawer, the Post screen does not get unmounted when navigating back to home, I keep gettings the same props and screen of the first post opened, over and over again.
This is my route setup:
import React from "react";
import { createDrawerNavigator, createStackNavigator, createAppContainer } from 'react-navigation';
import Home from './screens/Home';
import Post from './screens/Post';
import Settings from './screens/Settings';
import SideBar from './screens/sidebar';
const Drawer = createDrawerNavigator(
{
Home: {screen: Home},
Post: {screen: Post},
Settings: {screen: Settings}
},
{
initialRouteName: "Home",
backBehavior: 'initialRoute',
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
}
);
const AppNavigator = createStackNavigator(
{
Drawer: {screen: Drawer},
},
{
initialRouteName: "Drawer",
headerMode: "none",
}
);
export default createAppContainer(AppNavigator);
What am I doing wrong?
I want each post screen to open and re render as new when navigating to it from Home.
For those using react-navigation v5. I faced the same issue my component was not unmounting by using goBack() for a screen link in drawer. After little research I found our that in latest version of react-navigation they have added an attribute unmountonblur in screen options for drawer navigator by default its false that's why component doesn't unmount. I am using this to solve my problem.
Here is my code
<Drawer.Screen name="ResetLogsStack" component={ResetLogsStack} options={{unmountOnBlur:true}}/>
Here is the link for that: unmountonblur
I use to face the same issue. I made my screen Post listen to navigation focus event triggered by react-nativation here instead of componentDidMount.
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const Post = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus',payload)}
onDidFocus={payload => console.log('did focus',payload)} //
onWillBlur={payload => console.log('will blur',payload)}
onDidBlur={payload => console.log('did blur',payload)}
/>
{/*
Your view code
*/}
</View>
);
With onDidFocus, you may get the navigation param, fetch data, and/or update state. You may clear screen state with onDidBlur if needed.
Alternatively, you can do imperative coding as this doc here
Update :
By the way, I am wondering why you put Post with Drawer? Is it just to have a link in the drawer that can access to the Post page?
In my opinion, you should move Home and Post to new stack and make Home as initial Route. This will make sure that the Post is unmounted after navigating back to Home.
Check out my sample below
const HomeStack = createStackNavigatior({
Home: {screen: Home},
Post: {screen: Post},
}, {
initialRouteName: 'Home',
...
})
const Drawer = createDrawerNavigator(
{
HomeStack
Settings: {screen: Settings}
},
{
initialRouteName: "HomeStack",
backBehavior: 'initialRoute',
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
}
);
It's the way react-navigation works, to counter this you can add listeners, like so:
<NavigationEvents
onDidFocus={payload => { console.log(payload.state.params,'<- this has your new params') }}
/>
OR
this.props.navigation.addListener('didFocus', payload=>{console.log(payload)})
Check this for more information
I lost several hours on this after updating React Navigation from 2.14.2 to 3.11.0. The funny thing is that it was working perfectly and after upgrade pages were not re-rendering which was causing to wrong language and not actual data on the pages.
What I have found is the issue DrawerNavigator screen shouldn't unmount
So part of us complain why it is not rerendering screens and part was complaining why it was rerendering. The second part won the battle :)
Basically the solution was as expected: to listen to WillFocus event...
Just add this to code to your component if you want it to be rerendered on each visit.
/**
* When using React Navigation Component is not umnounted and instead stored in navigator.
* We need component to be rerendered during each visit to show right info and language
*/
componentDidMount() {
//this.props.navigation will come in every component which is in navigator
focusSubscription = this.props.navigation.addListener(
'willFocus',
payload => {
this.forceUpdate();//Native react function to force rerendering
}
);
this.setState({focusSubscription: focusSubscription});
}
componentWillUnmount() {
this.state.focusSubscription.remove();
}
I tried to use the method suggested in the answers but they didn't work perfectly with my case.
Solution:
I tried to flip createDrawerNavigator() with createStackNavigator() and it worked perfectly like before!
I don't have to use react navigation events, normal event like componentWillUnmount and componentWillMount work perfectly.
const MainScreens = createStackNavigator(
{
Home: {screen: Home},
Post: {screen: Post},
Settings: {screen: Settings}
},
{
initialRouteName: "Home",
headerMode: "none",
}
);
const AppNavigator = createDrawerNavigator(
{
Main: {screen: MainScreens},
},
{
initialRouteName: "Main",
backBehavior: 'initialRoute',
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
}
);
export default createAppContainer(AppNavigator);
Not sure if there's anything wrong in what I did, but its working fine until now.
As per Muhammad Zain response, if you are using react navigation v5, you can pass directly to the Drawer screen the option unmountOnBlur
options={{unmountOnBlur:true}}
and that will be enough.
React navigation docs
I'm using react-navigation for months. But placing screens directly into Drawer navigator prevents willunmount method to be called. First place screen in a stack navigator then place it into drawer navigator.

How to selectively show modals in React Navigation?

I'm building an app that is very similar to the Calendar app on iOS. I have a tab-based app that should sometimes trigger modals and other times cards, based on the screen being shown. In Apple's Calendar app, if you click on an event, you'll get a normal (slide from side or "card") transition, but if you click on the "+" button, you'll get a modal (bottom up) transition. I'd like to achieve something similar. The problem now is I can only get either all modals or all card transitions.
const CalendarTab = createStackNavigator(
{
CalendarView: {screen: CalendarView},
ViewEvent: {screen: ViewEvent}, // This should be a "card" transition
AddEvent: {screen: AddEvent}, // This should be a "modal" transition
},
{
mode: "modal", // This shouldn't be hard-coded
initialRouteName: "CalendarView",
}
);
const TabStack = createBottomTabNavigator({
Calendar: CalendarTab,
Settings: SettingsTab,
},
{
initialRouteName: "Calendar",
navigationOptions: ({ navigation }) => ({
tabBarVisible: navigation.state.index === 0, // This should only apply to modals
});
}
export default class App extends Component {
render() {
return <TabStack />;
}
}
Edit: I did find a partial solution here: https://github.com/react-navigation/react-navigation/issues/707#issuecomment-299859578, but it causes the headers to get messed up and is generally a brittle solution.

how to destroy a screen after navigating from it in StackNavigator

Using react navigation. I have a StackNavigator
const Stack = StackNavigator( // eslint-disable-line new-cap
{
List: {
screen: DrugPage,
},
Create: {
screen: DrugCreate,
},
},
{
initialRouteName: 'List',
}
);
The first screen is a list of entities and the second screen is to create a new entity that will add to the list. The first List screen has a nice link to 'Add Entity' in the navigation bar which goes to the Create route. After creating the entity I use navigation.navigate to go back to the List route. This leaves the create entity screen on the stack and so then a back button appears in the nav bar on my list screen. I don't want the Create Entity screen to remain in the stack after the entity is successfully created--I want it destroyed so Create screens don't build up in a stack that I don't need and so I don't have a back button I don't want on the List screen. I thought about using a StackNavigator but that doesn't give you a nice navbar at the top (in iOS). Any recommendations?
I had a problem very similar to yours, and after days searching I was able to solve my problem by adding a line of code, which in my case destroys the screen as soon as it leaves it.
add this to the properties of drawerNavigator : unmountInactiveRoutes: true
follows an example of my code :
const drawMenu = createDrawerNavigator({
StackHome,
StackOS
},{
unmountInactiveRoutes: true,
initialRouteName: 'StackHome'
}
);
I used the reset action in NavigationActions per #Pritish Vaidya's comment on my original question. (https://reactnavigation.org/docs/navigation-actions#reset)
Implementation
const resetAction = NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({routeName: 'List'})],
key: null,
});
navigation.dispatch(resetAction);
https://reactnavigation.org/docs/navigation-actions#reset
Based on the answers above, with little improvements this is what worked for me:
import { CommonActions } from '#react-navigation/native';
const SplashScreen = () => {
const isFocused = useIsFocused();
const navigation = useNavigation();
...
useEffect(() => {
if (!isFocused) {
navigation.dispatch(state => {
const routes = state.routes.filter(item => item.name !== 'SplashScreen');
return CommonActions.reset({ ...state, routes, index: routes.length - 1 });
});
}
}, [isFocused, navigation]);
...
return ...
}
The easiest way is to use pop(), then you navigate to main screen
navigation.pop();
navigation.navigate('List')

React Navigation - tutorial for nesting navigators: TabNavigator overwrites StackNavigator

While working through the React Navigation tutorial on nesting navigators, I find that the tab navigator, MainScreenNavigator, overwrites the stack navigator, SimpleApp. The result is that the tabbed screens called in that object are the only ones that are displayed. The expected behavior that does not occur is that the button which links to the nested navigator never displays, so I cannot access the ChatScreen object.
const MainScreenNavigator = TabNavigator({
Recent: { screen: RecentChatsScreen },
All: { screen: AllContactsScreen },
});
I've spent hours trying to understand what I might have missed. This is my first attempt at learning this package, so I don't know if the tutorial is wrong, or that I missed a detail that breaks the process. The entire App.js file is located here.
Your help is much appreciated.
First, you need to pass StackNavigator's navigation to screens of Tabs. Because they are only screens you can deal with now since it's a initial screen.
const MainScreenNavigator = TabNavigator({
Recent: { screen: ({screenProps}) => <RecentChatsScreen screenProps={screenProps}/>,
All: { screen: ({screenProps}) => <AllContactsScreen screenProps={screenProps}/> },
});
//I added the export keyword.
export const SimpleApp = StackNavigator({
Home: {
screen: ({navigation}) => <MainScreenNavigator screenProps={{myStackNavigation:navigation}}/>,
navigationOptions: {
title: 'My Chats',
},
},
Chat: { screen: ChatScreen },
})
Now, you can call below code in RecentChatsScreen or AllContactsScreen.
this.props.navigation.screenProps.myStackNavigation.navigate('Chat');

How to navigate to different screen and get back to the screen from where its navigated using DrawerNavigator?

I have two components (List and Detail):
List.js:
export default class List extends React.Component {
render() {
const {navigate} = this.props.navigation;
return (
<View style={styles.container}>
<Text style={styles.headerText}>List of Contents</Text>
<Button onPress={()=> navigate('Detail')} title="Go to details"/>
</View>
)
}
}
Detail.js:
export default class Detail extends React.Component {
render() {
return (
<View>
<Text style={styles.headerText}>Details of the content</Text>
</View>
)
}
}
I would like to have two screens (NowList and SoonList) which are basically the same type of list, so I am using the same List component for both the screen. And from each of these screens I want to navigate to the Details of the item, also which in this case has the same type of layout so I am using Detail component for both the list items.
Finally, when the App starts I want the NowList screen to show. And using the drawer I would like to navigate to SoonList screen.
I am not sure how to configure this route. But, this is how I have configured the routes for now:
const NowStack = StackNavigator({
NowList: {
screen: List,
navigationOptions: {
title: 'Now',
}
},
Detail: {
screen: Detail,
navigationOptions: {
title: 'Detail',
}
}
});
const SoonStack = StackNavigator({
SoonList: {
screen: List,
navigationOptions: {
title: 'Soon',
}
}
});
export const Drawer = DrawerNavigator({
Now: {
screen: NowStack,
},
Soon: {
screen: SoonStack,
}
});
When I navigate from NowList route to Detail route. There's a back button in the Detail route which on press navigates back to the NowList.
However, when I go to Soon route, and navigate to Detail route, I go to Detail screen. But, when I press the back button on Detail navigation header, instead of navigating back to the SoonList screen, I am navigated to the NowList screen.
I think I am missing something here, or my route layout is not how its suppose to be. Could you help me how to configure the routes so that I can use DrawerNavigator to navigate to different screens, and from those screens navigate to another screen and again back to the screen navigated from?
You can make a stack navigator that contain your drawer navigator and detail, this way you can access both now list and soon list from drawer and able to navigate to detail screen from both list.
const App = StackNavigator({
Drawer: {
screen: Drawer,
},
Detail: {
screen: Detail,
navigationOptions: {
title: 'Detail',
},
},
});
const Drawer = DrawerNavigator({
NowList: {
screen: List,
},
SoonList: {
screen: List,
},
});
Your route layout doesn't specify a Detail screen in the SoonStack navigator. When you navigate to Detail, react-navigation navigates to the only screen that is named that way. Since it is in the NowStack, going back returns to the first screen in the stack.
To solve this, you can add another Detail screen to the SoonStack navigator, however you'd want to either name it differently, or navigate to each Detail screen using its key.
In order to be able to navigate to the right Detail screen, you can add a parameter to each stack navigator.
For example:
const SoonStack = StackNavigator({
SoonList: {
screen: List,
navigationOptions: {
title: 'Soon',
}
},
SoonDetail: {
screen: Detail,
navigationOptions: {
title: 'Detail',
}
}
},{
initialRouteParams: {
detailScreen: 'SoonDetail'
}
});
You can then use the parameter to navigate to the correct screen.
Also, it's possible to only use a single StackNavigator with the three distinct screens in it, and reset the stack to the correct screen when switching with the drawer.
You can read more about reset here.
how to use reset
If you have a single stack navigator:
const TheStack = StackNavigator({
NowList: { screen: List, ... },
SoonList: { screen: List, ... },
Detail: {screen: Details, ... }
});
Then the drawer can reset the stack state to be whatever you want. For example, if the first screen was NowList and SoonList is clicked in the drawer, you can call:
const action = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({routeName: 'SoonList'});
]
});
this.props.navigation.dispatch(resetAction);
This would cause the stack to reset and have the SoonList as the first screen. If Details is opened, then it is the second in the stack, and back will always go back to the correct screen.