How to remove a route from react navigation stack? - react-native

So in my react-native project i have a Stack.Navigator like this.
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Splash" component={SplashScreen} />
{authState.isLoggedIn && authState.token !== "" ? (
<>
<Stack.Screen name="Root" component={Root} />
<Stack.Screen name="Detail" component={DetailScreen} />
</>
) : (
<>
<Stack.Screen name="Welcome" component={WelcomeScreen} />
<Stack.Screen name="SignIn" component={SignInScreen} />
</>
)}
</Stack.Navigator>
SignIn Function:
const onSignIn = () => {
dispatch(
signIn({
/// ...userData,
isLoggedIn: true,
})
);
};
Flow when opening App:
isLoggedIn = false, Splash -> Welcome (Press Button in Welcome) -> SignIn (Press Login and update isLoggedIn flag to true) -> Splash -> Root
What i expect is that after the user has login from SignIn page, the user doesn't need to see Splash page again. I have read about CommonActions and StackActions but i don't understand how to properly use it.
Note: I'm using redux to handle the authState and even though that i don't manually navigate it, it is already navigate to Splash and Root Page
Have tried this but still does not work
const onSignIn = () => {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: "Splash" }],
})
);
dispatch(
signIn({
/// ...userData,
isLoggedIn: true,
})
);
};

this will reset navigation stack to the route you are navigating and every thing else will be cleared so when user press back button there will be no screen to go back to.
import { CommonActions } from '#react-navigation/native';
use navigateToScreen method and pass route name you want to navigate to
//---- CLEAR STACK AND REDIRECT USER TO ANOTHER SCREEN ----//
const navigateToScreen = (name) => {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name },],
})
);
}

Related

How can i notify to App for what reload the app to show another screen?

I am in login screen i pressed the button login so
that button have this action
store.dispatch({
type: 'login',
payload: {
name: email.value
},
})
on the reducers change the value
export default function (state, action) {
switch (action.type) {
case 'login':
return {
...state,
user: action.payload,
isLogged:true
}
default:
return newState
}
}
now i have to notify to App.js for the new change because that is depend to show the stack.screen
export default function App() {
return (
<Provider store={store} theme={theme}>
<Tabs store={store} />
</Provider>
)
}
How can i notify to App for what reload the app to show another screen?
but never my component is updated, i was trying to update my component using useEffect but not
function Tabs ({ user }) {
const {
data: { isLogged },
} = useSelector((state) => state)
useEffect(() => {
console.log(1234546)
}, [isLogged, user])
return (
<Stack.Navigator >
{ !store.getState().data.isLogged ?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/> :
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>}
</Stack.Navigator>
)
When working with States Or Redux We don't have to tell the app about when to re-render the JSX code.
It will be re-renders automatically whenever the change occurs in redux or state.
But, We must have to be assured that we are not mutating the data in our state or redux, We always have to assign a new object to state or redux.
You can try the below changes in your code,
function Tabs ({ user }) {
const { isLogged } = useSelector((state) => state?.data)
return (
<Stack.Navigator >
{
isLogged
?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
:
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
}
</Stack.Navigator>
)
}
App navigator need to subscribe isLogged state then rerender accordingly.
import { useEffect } from "react";
import { useSelector } from "react-redux";
const App = () => {
const isLogged = useSelector((state) => state.isLogged);
// Track authentication state
useEffect(() => {}, [isLogged]);
return (
<Stack.Navigator>
{!isLogged ? (
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
) : (
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
)}
</Stack.Navigator>
);
};

For react-navigation + web, directly navigating to a nested route via URL results in missing back button in the header

I have HomeScreen with a link that goes to DeckScreen. When I click a button to navigate to the DeckScreen, the back button in the header bar shows up fine.
But when I reload the page in browser or directly navigate to this URL (localhost/deck), there is no back button.
And clicking on the BottomTab doesn't do anything, will not take us back Home.
I am using BottomTab that has a HomeStack, which contains the HomeScreen and DeckScreen.
export default function Navigation () {
return (
<NavigationContainer linking={linking} theme={DefaultTheme}>
<RootNavigator/>
</NavigationContainer>
);
}
function RootNavigator () {
return (
<Stack.Navigator>
<Stack.Screen name='Root' component={Nav} options={{headerShown: false, ...fade}}/>
<Stack.Group screenOptions={{presentation: 'modal'}}>
<Stack.Screen name='Modal' component={ModalScreen}/>
</Stack.Group>
</Stack.Navigator>
);
}
function HomeStackScreen () {
return (
<HomeStack.Navigator initialRouteName='dashboard'>
<HomeStack.Screen name='dashboard' component={HomeScreen} options={{headerShown: false, title: 'Dashboard'}}/>
<HomeStack.Screen name='deck' component={DeckScreen} options={{title: 'Deck'}}/>
</HomeStack.Navigator>
);
}
function Nav ({navigation}) {
return (
<BottomTab.Navigator
initialRouteName='home'
screenOptions={{
headerShown: false,
}}>
<BottomTab.Screen
name='home'
component={HomeStackScreen}
})}
/>
</BottomTab.Navigator>
);
}
And here is my Linking:
const linking: LinkingOptions<RootStackParamList> = {
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
Root: {
screens: {
home: {
screens: {
dashboard: 'dashboard',
deck: 'deck'
},
}
},
}
}
}
};
I've tried using getStateFromPath to try to inject a route in stack but it doesn't work and feels wrong.
How do you tell React Navigation, this screen is part of a stack, and it should always have a back button in that header?
The reason why there's no back button when you're opening from the link is most likely because you don't set headerLeft in the screen and there's no other screen in the navigation stack (you went directly to the DeckScreen).
You can set the back button in the option in Screen, like this example below:
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>
</Stack.Navigator>
);
}
You can find the example here

How to hide some pages from Drawer Navigation but still be able to navigate to them - React Native?

I want to hide some pages from the Drawer
I want to hide some pages from the Drawer (for example hide the SignUpPage and SuccessPage), how can I do it ?
i also tried to make an anonymous function in the DrawerLabel [ ()=> null ] but it is still not a good solution because even tho it shows me an empty label, yet when i click on it , it navigates me to the page that i wanted to hide.
Please help
and thanks for all the helpers :)
import { createDrawerNavigator } from '#react-navigation/drawer';
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
<Drawer.Screen
name="SignUpPage"
component={SignUpPage}
options={{ drawerLabel: 'SignUp Page' }}
/>
<Drawer.Screen
name="SuccessPage"
component={SuccessPage}
options={{ drawerLabel: 'SuccessPage' }}
/>
</Drawer.Navigator>
);
}
const Stack = createStackNavigator();
export default function App() {
return (
< NavigationContainer >
<DrawerNavigator>
<Stack.Navigator initialRouterName="WelcomePage">
<Stack.Screen name="WelcomePage" component={WelcomePage} />
>
<Stack.Screen name="SuccessPage" component={SuccessPage} />
<Stack.Screen name="HomePage" component={HomePage} />
</Stack.Navigator>
</DrawerNavigator>
</NavigationContainer >
);
}
You have differents options
I guess you want to hide that options when your user is signed or not. With v5 you can do the code below. The another option is the same but playing with custom content that is a bit complex also I give you the docs if you want the complex solution https://reactnavigation.org/docs/drawer-navigator.
DrawerNavigator
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
</Drawer.Navigator>
);
}
AuthNavigator
const Stack = createStackNavigator<AuthParamList>();
export const AuthNavigator = () => {
return (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='SignUpPage' component={SignUpPage}></Stack.Screen>
<Stack.Screen name='SuccessPage' component={SuccessPage}></Stack.Screen>
</Stack.Navigator>
);
};
IsAuthScreen, I use firebase + redux so here you need to put your login logic
const IsAuth: React.FC<RoutesProps> = (props) => {
const { eva, ...rest } = props;
const dispatch = useDispatch();
const onAuthStateChanged = (currentUser: any) => {
console.log("onAuthStateChanged -> currentUser", currentUser)
if (!currentUser) {
dispatch(new authActions.DidTryLogin());
} else {
if (!currentUser.emailVerified) {
dispatch(new authActions.DidTryLogin());
} else {
dispatch(new authActions.SigninSuccess(currentUser));
dispatch(new settingsActions.GetProfile(currentUser.uid));
}
}
};
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(onAuthStateChanged);
return () => {
subscriber();
}; // unsubscribe on unmount
}, [dispatch]);
return (<View >
<LoadingIndicator size='large' /> // Here put a loading component
</View>);
};
App component, I use redux for check if my user is logged so here you need to put your own logic
const isAuth = useSelector(selectAuthUser);
const didTryAutoLogin = useSelector(selectAuthDidTryLogin);
return (
<NavigationContainer>
{isAuth && <DrawerNavigator />}
{!isAuth && didTryAutoLogin && && <AuthNavigator />}
{!isAuth && !didTryAutoLogin && <IsAuthScreen />}
</NavigationContainer>);
So when you logout you don't need to navigate to SignInScreen (this will be a problem if you think about it because if you want to do that you can back to protected screens with the back button or gesture). You only need to update the state and the correct navigator will be in place and you can put the default screen to show. You can achieve this with redux or react context.

How to navigate to a screen on back button press in React navigation 5 stack navigator

There are many similar questions that have been answered but none of them uses the newest react-navigation version.
I want to go to the 'Home' screen from the 'Document' screen on back button press. This is what I tried but it doesn't work.
<NavigationContainer>
<Stack.Navigator initialRouteName="Home" screenOptions={{ headerShown : false }}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Camera" component={CameraScreen} />
<Stack.Screen name="Document" component={Document} options={{
headerLeft: (props) => (<HeaderBackButton {...props} onPress={() => props.navigation.navigate("Home")}/>)
}} />
</Stack.Navigator>
</NavigationContainer>
Edit for more clarification: My app starts with 'Home' then goes to 'Camera' then to 'Document'. Now, I don't want to go back to 'Camera' once I am at 'Document' rather straight to 'Home' when I press the phone's back button.
According to the documentation, this is how to override the back button.
<Screen
name="Home"
component={HomeScreen}
options={{
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
// Do something
}}
/>
),
}}/>;
But I don't know how to go to 'Home' using the above code. So I searched similar questions and most of them had navigationOptions and other stuff. I tried following the below answer from a question.
import { HeaderBackButton } from 'react-navigation';
static navigationOptions = ({navigation}) => {
return{
headerLeft:(<HeaderBackButton onPress={()=>{navigation.navigate('A')}}/>)
}
}
So yeah, the back button doesn't respond even if I use console.log
You can customise back button behaviour using navigation 'beforeRemove' event, which fires before unloading a screen.
useEffect(() => {
const unsubscribe = navigation.addListener('beforeRemove', e => {
e.preventDefault(); // Prevent default action
unsubscribe() // Unsubscribe the event on first call to prevent infinite loop
navigation.navigate('Home') // Navigate to your desired screen
});
}, [])
The positive point with this approach is that it will fire on any type of back navigation, whether it's in-app back button or device back button/gesture.
Navigation Events Doc
I managed to achieve what I needed. I had to override the physical back button in 'Document'.
useFocusEffect(
useCallback(() => {
const onBackPress = () => {
navigation.pop(2); // remove two screens i.e. Document and Camera
return true // disable normal behaviour
};
BackHandler.addEventListener('hardwareBackPress', onBackPress); // detect back button press
return () =>
BackHandler.removeEventListener('hardwareBackPress');
}, [])
);
I am not sure how your code works.
<it doesn't work.> - you can give us more information.
But it's worth to try below.
import {CommonActions} from '#react-navigation/native';
props.navigation.dispatch(
CommonActions.navigate({name: 'Home'}),
);
https://reactnavigation.org/docs/navigation-prop/#dispatch
Did you try CommonActions.reset ?
something like this from the doc :
import { CommonActions } from '#react-navigation/native';
// when you want to navigate to the Documents page, instead of doing
navigation.navigate({routeName: 'Documents'});
// you can try
navigation.dispatch(
CommonActions.reset({
index: 1,
routes: [
{ name: 'Home' },
{
name: 'Documents',
params: { name: 'abc' },
},
],
})
);
So that when you're going back from Documents you're heading to the previous screen in the stack : Home.

Disable navigating back to Login/Signup screen in react navigation 5.x

I am using React Navigation 5
My structure is like this:
ROOT (STACK)
|-- LoginStack (STACK)
| |-- Login (SCREEN) -> when successful navigate to "Mainapp_stack"
| +-- Register (SCREEN) -> after registration, navigate to "Mainapp_stack"
|
+-- Mainapp_stack (STACK)
|-- Dashboard (SCREEN)
|-- MyProfile (SCREEN)
It is ok by checking usertoken I can able to navigate to the main appstack but,
At the very first time in the process of Login/Registration how to prevent user navigating back after successful Login/Registration in react-navigation 5.x
App.js
<NavigationContainer>
<Stack.Navigator>
{this.state.token_available ?
<Stack.Screen
name="Mainapp_stack"
component={Mainapp_stack}
options={{headerShown: false}}
/>
:
<>
<Stack.Screen
name="TeacherLogin"
component={TeacherLogin}
/>
<Stack.Screen
name="Info"
component={Info}
/>
<Stack.Screen
name="Mainapp_stack"
component={Mainapp_stack}
/>
</>
}
</Stack.Navigator>
</NavigationContainer>
Mainapp_stack.js
<Stack.Navigator initialRouteName='Dashboard'>
<Stack.Screen
name="Dashboard"
component={Dashboard}
// options={{headerShown: true}}
/>
</Stack.Navigator>
Now when I complete log in/Registration, I do not want to navigate back if I press the hardware back button. My variable token_available is in App.js and I am not using redux.
So, How can I solve it?
in your main screen use this code:
import { useFocusEffect } from "#react-navigation/native";
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
Alert.alert("Hold on!", "Are you sure you want to Exit?", [
{
text: "Cancel",
onPress: () => null,
style: "cancel"
},
{ text: "YES", onPress: () => BackHandler.exitApp() }
]);
return true;
};
BackHandler.addEventListener("hardwareBackPress", onBackPress);
return () =>
BackHandler.removeEventListener("hardwareBackPress", onBackPress);
}, []));
read more here: React navigation documents