how to destroy a screen after navigating from it in StackNavigator - react-native

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')

Related

React Navigation - goBack() is automatically invoked for stack navigator screen

I have 3 stack navigator screens (Home, Item List, Item Detail -> same order) inside drawer navigator and all three screens are hooked up to redux store with connect() helper.
When I do navigate('ItemDetail') from ItemList, it navigates to the screen and immediately comes back to ItemList screen. I am not sure why.
Following is my navigation structure -
const AppStack = createStackNavigator(
{
Home: {
screen: HomeScreen
},
ItemList: {
screen: ItemListScreen
},
ItemDetail: {
screen: ItemDetailScreen
}
},
{
initialRouteName: 'Home'
}
);
const DrawerNav = createDrawerNavigator(
{
DrawerApp: AppStack
},
{
drawerPosition: 'right'
}
);
const SwitchStack = createSwitchNavigator(
{
Loading: Loading,
Auth: AuthStack,
App: DrawerNav
},
{
initialRouteName: 'Loading'
}
);
This is how my each navigation screen component looks -
export class ProviderListScreen extends Component {
render() {
const { navigation } = this.props;
// ItemList is hooked up to Redux via connect() helper
return <ItemList navigation={navigation} />;
}
On my ItemDetail component, I get the Item data through route params from ItemList screen and I also dispatch an action (To reset some part of the store state) in component did mount. As soon as I do that, previous screen (ItemList) is automatically rendered.
Inside item detail, I make API call to create booking for that item and the booking object is managed by redux. Once I land on the ItemDetail, I reset the booking object for new booking data.
Here is the snippet of ItemDetail's componentDidMount -
componentDidMount() {
this.props.resetItembooking();
}
I am not sure what is causing this behaviour. If I remove the ItemList screen and jump directly to ItemDetail screen from HomeScreen, this issue does not occur.
Any help is appreciated.
I had the exact same problem however I tried the answer given in the original post comments sections given by Awadhoot but this did not work for me.
For anyone still trying to solve this issue, ensure you do not have any recurring intervals setup. Therefore you should always clear intervals before navigating away:
clearInterval(this.intervalId);
In react-navigation 5 you can use the useIsFocused hook for that:
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}
From the docs: https://reactnavigation.org/docs/use-is-focused/

Finish current component while navigating to next component using React Native Navigation?

I wanted to close the current component completely while navigating to next component in react-native.
I am using react-navigation for navigating between screens.
Scenario is, I am having two js in my project, Login.js and Home.js. When user logs in into the app it saves the credentials in the AsyncStorage. Every-time when user comes to Login Screen it checks for whether user is logged in already or not. If the user is logged in then app will directly navigate you to the Home page, at this action I want to close the login screen completely.
Currently with my implementation the Login screen remains in to the navigation stack. When I press back from the Home page the app should be closed completely and should not relaunch with login screen again.
Here is my StackNavigator code :
const navigationStack = createStackNavigator(
{
Login: {
screen: LoginScreen
},
Home: {
screen: HomeScreen
},
},
);
For navigating :
this.props.navigation.navigate('Home');
Please let me know what I am doing wrong with my existing code?
You can implement this by multiple ways. Like using replace or reset action on stack Navigator, or using switch Navigator instead of stack Navigator.
Using Reset: (Empty stack and navigate to specified screen)
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Home' })],
});
this.props.navigation.dispatch(resetAction);
Using replace: (replace current screen with the specified screen)
this.props.navigation.replace("Home");
Using Switch Navigator:(Recommended)
const navigationStack = createSwitchNavigator(
{
Login: {
screen: LoginScreen
},
Home: {
screen: HomeScreen
},
},
);
// Navigate to screen
this.props.navigation.navigate("Home");
This can be achieved without having to add back handling code to each and every screen by modifying the getStateForAction method of the particular StackNavigator's router.
const navigationStack = createStackNavigator(
{
Login: {
screen: LoginScreen
},
Home: {
screen: HomeScreen
},
},
);
The getStateForAction method can be modified to achieve this
const defaultStackGetStateForAction =
navigationStack.router.getStateForAction;
navigationStack.router.getStateForAction = (action, state) => {
if(state.index === 0 && action.type === NavigationActions.BACK){
BackHandler.exitApp();
return null;
}
return defaultStackGetStateForAction(action, state);
};
the state.index becomes 0 only when there is one screen in the stack.
You can check with this Back Handling

this.props.navigation vs NavigationActions in react-navigation

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.

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.

How do i make a TabNavigator button push a modal screen with React Navigation

Using the React Navigation tab navigator https://reactnavigation.org/docs/navigators/tab how do I make one of the tab buttons push the screen up as a full screen modal? I see the stack navigator has a mode=modal option. how do I get that mode to be used when clicking on the TakePhoto tab button? Clicking on it currently still shows the tab bar on the bottom.
const MyApp = TabNavigator({
Home: {
screen: MyHomeScreen,
},
TakePhoto: {
screen: PhotoPickerScreen, // how can I have this screen show up as a full screen modal?
},
});
Actually, there is no support in react-navigation to change the way of presentation on the fly from default to modal (see the discussion about this here). I ran into the same issue and solved it by using a very top StackNavigator with headerMode set to none and mode set to modal:
const MainTabNavigator = TabNavigator(
{
Tab1Home: { screen: Tab1Screen },
Tab2Home: { screen: Tab2Screen }
}
);
const LoginRegisterStackNavigator = StackNavigator({
Login: { screen: LoginScreen }
});
const ModalStackNavigator = StackNavigator({
MainTabNavigator: { screen: MainTabNavigator },
LoginScreenStackNavigator: { screen: LoginRegisterStackNavigator }
}, {
headerMode: 'none',
mode: 'modal'
});
This allows me to do the following (using redux) in Tab1Screen and Tab2Screen to bring up the modal view from wherever I want:
this.props.navigation.navigate('LoginScreenStackNavigator');
Not sure if this is still relevant for you, but i've managed to find away to achieve this.
So i've managed to get it working by using the tabBarComponent inside the tabNavigatorConifg, you can stop the tab navigation from navigating depending on the index.
tabBarComponent: ({jumpToIndex, ...props, navigation}) => (
<TabBarBottom
{...props}
jumpToIndex={index => {
if (index === 2) {
navigation.navigate('camera')
}
else {
jumpToIndex(index)
}
}}
/>
)
Once you've done this, my method of showing the view modally on top of the tab views was to put the tabnavigator inside of a stacknavigatior and then just navigate to a new screen inside of the stacknavigator.
react-navigation's bottomTabNavigator has a tabBarOnPress navigation option you can use to override tab presses:
https://reactnavigation.org/docs/en/bottom-tab-navigator.html#tabbaronpress
const AppContainer = createStackNavigator(
{
default: createBottomTabNavigator(
{
TAB_0: Stack0,
TAB_1: Stack1,
TAB_2: Stack2,
TAB_3: View // plain rn-view, or any old unused screen
},
{
defaultNavigationOptions: {
// other tab navigation options...
tabBarOnPress: ({ navigation, defaultHandler }) => {
if (navigation.state.key === 'TAB_3') {
navigation.navigate('tabToOpenAsModal');
} else {
defaultHandler();
}
}
}
}
),
tabToOpenAsModal: {
screen: TabToOpenAsModalScreen
}
},
{
mode: 'modal',
headerMode: 'none'
}
);
If you nest your tab navigator within a stack navigator with a modal, you can open this when the tab bar button is pressed. Since the modal was opened and not the tab, when the modal is closed the app returns to the screen that was visible before the modal tab was pressed.
One way to make the tab bar go away is to hide the tabBar with visible: false:
const MyApp = TabNavigator({
Home: {
screen: MyHomeScreen,
},
TakePhoto: {
screen: PhotoPickerScreen,
navigationOptions: {
tabBar: {
visible: false,
},
},
},
});
However, that does not seem to trigger any transition to fullscreen, which I guess is desired?
Another option could be to wrap PhotoPickerScreen inside a new StackNavigator and set that navigator to mode='modal'.
You might have to trigger the navigation to that modal from onPress on the tabItem somehow (eg. navigation.navigate('TakePhoto').)
Note, I'm trying to wrap my head around how best to structure navigation myself, so …
Third option, implementing a StackNavigator as parent, then adding the MyApp TabNavigator as the first route inside of it, could be the most flexible solution. Then the TakePhoto screen would be on the same level as the TabNavigator, allowing you to route to it from wherever.
Interested to hear what you come up with!
I suggest two solution
First one is about hide it
For seconde one please read that: https://reactnavigation.org/docs/hiding-tabbar-in-screens
<Tab.Screen
name={Routes.CREATE_ANNOUNCEMENT_SCREEN}
// name={Routes.TEST_SCREEN}
options={{
.
.
.
tabBarVisible: false, <----- solution 1
tabBarButton: (props) => ( <----- or solution 2
<TouchableOpacity
{...props}
onPress={() => {
navigation.navigate(Routes.DETAILS_SCREEN);
}}
/>
),
}}
component={CreateAnnouncementScreen}
/>
Docs are patchy in some areas, but here it is:
export default class HomeScene extends Component {
static navigationOptions = {
title: 'foo',
header:{
visible: true
}
}
....
}