How to reset nested navigators (react-navigation v5) - reactjs

Having two sets of stack navigators;
const SetOneScreens = () => (
<Stack.Navigator initialRouteName="AScreen">
<Stack.Screen name="AScreen" component={AScreen} />
<Stack.Screen name="BScreen" component={BScreen} />
</Stack.Navigator>
);
const SetTwoScreens = () => (
<Stack.Navigator initialRouteName="CScreen">
<Stack.Screen name="CScreen" component={CScreen} />
<Stack.Screen name="DScreen" component={DScreen} />
</Stack.Navigator>
);
Which are nested in a Drawer navigator
<NavigationContainer>
<Drawer.Navigator initialRouteName="SetOneScreens">
<Drawer.Screen name="SetOneScreens" component={SetOneScreens} />
<Drawer.Screen name="SetTwoScreens" component={SetTwoScreens} options={{swipeEnabled: false}} />
</Drawer.Navigator>
</NavigationContainer>
I want to navigate From BScreen to DScreen and reset the stack (in order to not letting hardware back button in android get back to BScreen)
As in the nesting situation, we should first define the navigator name; how should I define the screen in reset action.
// For navigation
props.navigation.navigate('setTwoScreens',{screen:'DScreen'})
// For reset I can only navigate to initial screen
props.navigation.reset({index:0,routes:[{name:'setTwoScreens'}]})
How should I handle the reset with navigation or CommonActions

As written in the documentation of react-navigation-v5, you need to dispatch CommonAction with reset-action to clear back-stack of your application, so that application doesn't go back to previous screen when user press hardware back-button of device, check below example,
import { CommonActions } from "#react-navigation/native";
props.navigation.dispatch({
CommonActions.reset({
index: 0,
routes: [{ name: "AnotherStackNavigator" }]
})
});
Or if you want to reset to specific screen in that StackNavigator you can do like this:
props.navigation.dispatch({
CommonActions.reset({
index: 0,
routes: [
{
name: "AnotherStackNavigator",
state: {
routes: [
{
name: "AnotherStackNavigatorScreen",
params: {
...
}
}
]
}
}
]
})
});

I tried the solution from Aditya, but had issues with the back button not going back to the root of the new stack.
This seemed to fix that issue for me though:
props.navigation.dispatch({
CommonActions.reset({
index: 0,
routes: [
{
name: "AnotherStackNavigator",
state: {
routes: [
{
name: "AnotherStackNavigatorRootScreen"
},
{
name: "AnotherStackNavigatorScreen",
params: {
...
}
}
]
}
}
]
})
});

Related

How can i hide a tab in bottom navigation - react native

Can someone please advise how can i hide a tab from display in Bottom Navigation, i want to jumpTo the tab from code but i don't want it show in the Bottom Navigation. Below is some code i am using, i just want to hide the vehicle_edit tab from bottom but i want to keep using it in my code via jumpTo,
please if someone can advise.
//routes
const [routes] = React.useState([
{ key: "dashboard", title: "Dashboard", icon: "home" },
{ key: "notifications", title: "Notifications", icon: "bell" },
{ key: "account", title: "My Account", icon: "account" },
{ key: "vehicle_edit", title: "Vehicles", icon: "car" },
{ key: "appointments", title: "Appointments", icon: "calendar" },
]);
//set bottom tab to first always
useEffect(() => {
setIndex(0);
}, []);
//screens
const renderScene = BottomNavigation.SceneMap({
dashboard: DashboardScreen,
notifications: NotificationsScreen,
account: AccountScreen,
vehicle_edit: VehicleEditScreen,
appointments: AppointmentsScreen,
});
//render
return <BottomNavigation navigationState={{ index, routes }} onIndexChange={setIndex} renderScene={renderScene} />;
react-native-paper Bottom Navigation component design for only for Bottom navigation screen.
You need stack navigation for vehicle_edit. You can simply achieve this by using React navigation stack navigator
Brilliant thanks that solved it..
const Stack = createStackNavigator();
const Account = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Account" component={AccountScreen} />
<Stack.Screen name="VehicleEdit" component={VehicleEditScreen} />
</Stack.Navigator>
);
};
//screens
const renderScene = BottomNavigation.SceneMap({
dashboard: DashboardScreen,
notifications: NotificationsScreen,
account: Account,
appointments: AppointmentsScreen,
});

Get everything after the prefix as a param with Deeplinking and React-Navigation-5

I am using react-navigation-5 and deeplinking. I would like to extract everything after the deeplink prefix and pass it as a param.
I have the following
const deeplinking = {
prefixes: ['myapp://'],
config: {
Store: {
path:'store/:url' ,
params: {
url: null,
},
}
}
};
return (
<NavigationContainer linking={deeplinking}>
<Stack.Navigator initialRouteName="Splash" screenOptions={{ headerShown: false, cardStyle: { backgroundColor: '#fff' } }}>
<Stack.Screen name="Splash" component={Splash} />
<Stack.Screen name="Store" component={Store} />
</Stack.Navigator>
</NavigationContainer>
);
}
If I use the following deep-link:
myapp://alpha/delta/gamma
The url parameter becomes:
'alpha'
So the first forward-slash / encountered after the prefix and everything after it is getting stripped out when the deeplink is passed.
I want the parameter (url in the above example), to be:
'alpha/delta/gamma'
How can I achieve this?
I have checked the docs here:
https://reactnavigation.org/docs/deep-linking/
https://reactnavigation.org/docs/configuring-links/
But they do not mention how to deal with forward slashes.
config: {
screens: {
deeplink: {
path: 'deeplink/id',
},
},
},
}
Make deeplink url:
https://example.com/deeplink/details?token=uygiuhiuhpuihpiuyiuhuihhiluhiu
Access token from component:
this.props.route.params.token
result will be =uygiuhiuhpuihpiuyiuhuihhiluhiu

TypeError: navigation.getParam is not a function

I am using react navigation with stack 5 version.
App.js
export default class App extends React.Component {
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Movie" component={Movie} />
</Stack.Navigator>
</NavigationContainer>
)
}
}
Home.js
<TouchableHighlight onPress={() => { this.props.navigation.navigate('Movie', { title: item.title, img: item.img, id: item.imdbID }) }}>
// code omitted
</TouchableHighlight>
I clicked movie item to movie details but I am getting error message and it says:
TypeError: navigation.getParam is not a function
Movie.js
componentDidMount() {
this.getMovieDetails(this.props.navigation.getParam('id', 'n/a'))
}
getMovieDetails = async (id) => {
const results = await fetchById(id)
this.setState({ info: results })
}
render() {
const { navigation } = this.props
const title = navigation.getParam('title', 'N/A');
const img = navigation.getParam('img', 'https://banner2.kisspng.com/20180216/kee/kisspng-photographic-film-reel-clip-art-movie-film-5a8677562304e0.0541516415187618141435.jpg');
}
Could you please help me with this issue?
I am waiting for your response.
Thanks in advance
Here is a snack with the solution that you required, I have made a few changes in your code and your error is fixed now in this snack.
You were trying to access getParams function but that wasn't available inside the navigation object so instead of getting params with getParams function I directly get these params from this.pros.navigation.route.params
https://snack.expo.io/#waheed25/home_movie
This is a common issue in react-native while passing props with navigation. You could try this approach to see if it works.
//Home.js
<TouchableHighlight onPress={() => { this.props.navigation.navigate({'Movie', { title: item.title, img: item.img, id: item.imdbID }}) }}>
And in the Movie.js you can access the props like this.
//Movie.js
componentDidMount() {
this.getMovieDetails(this.props.route.params)
}
Update:
//Home.js
<TouchableHighlight onPress={() => { this.props.navigation.navigate('Movie', { title: item.title, img: item.img, id: item.imdbID }) }}>
// code omitted
</TouchableHighlight>
//Movie.js
componentDidMount(){
const navObject = this.props.navigation.state.params
console.log(navObject )
}
this.state = {
do:{
name: props.route.params.name,
color: props.route.params.color
}
};

React Router v5.1.2 Public & Protected Authenticated & Role Based routes

Goal is to have /login as the only public route, once logged in user has routes based on user role.
Authentication is done with Keycloak I get users from keycloak.idTokenParsed.preferred_username: admin, manager, engineer, operator.
If operator tries to go to role restricted route gets redirected to /notauthorized page. (This part not done)
If not logged in user gets redirected to /login page. (This part is done/works)
Is there a better way to do this? Not repeating routes & adding additional users in Routes.jsx kind of a mess.
How do I implement role restricted redirect to /notauthorized?
App.js (does not have all the imports and missing bottom part with mapStateToProps, mapDispatchToProps & export default App )
import React, { useEffect } from "react";
import { Route, Redirect, Switch } from "react-router-dom"
let routeWithRole = [];
let user = '';
const AppContainer = ({ keycloak }) => {
if(keycloak && keycloak.token) {
user = keycloak.idTokenParsed.preferred_username
if( user === 'admin') {
routeWithRole = admin;
} else if( user === 'engineer') {
routeWithRole = engineer
} else if(user === 'manager') {
routeWithRole = manager
} else {
routeWithRole = operator
}
}
return (
<div>
{(keycloak && keycloak.token) ?
<React.Fragment>
<Switch>
{routeWithRole.map((prop, key) => {
console.log('App.js Prop & Key ', prop, key)
return (
<Route
path={prop.path}
key={key}
exact={true}
component={prop.component}
/>
);
})}
<Redirect from={'/'} to={'/dashboard'} key={'Dashboard'} />
</Switch>
</React.Fragment>
:
<React.Fragment>
<Switch>
{publicRoutes.map((prop, key) => {
return (
<Route
path={prop.path}
key={key}
exact={true}
component={(props) =>
<prop.component
keycloak={keycloak}
key={key} {...props} />
}
/>
);
})}
<Redirect from={'/'} to={'/login'} key={'login'} />
</Switch>
</React.Fragment>
}
</div>
)
}
Routes.jsx (missing all the impotrs)
export const publicRoutes = [
{ path: "/login", type: "public", name: "landing page", component: LandingPageContainer },
]
export const admin = [
{ path: "/createUser", name: "Create User", component: CreateUser},
{ path: "/editUser", name: "Edit User", component: EditUser},
{ path: "/createdashboard", name: "Create Dashboard", component: CreateDashboard },
{ path: "/editashboard", name: "Edit Dashboard", component: EditDashboard },
{ path: "/createcalendar", name: "Create Calendar", component: CreateCalendar },
{ path: "/editcalendar", name: "list of factories", component: EditCalendar },
{ path: "/dashboard", name: "Dashboard", component: Dashboard }
]
export const engineer = [
{ path: "/createdashboard", name: "Create Dashboard", component: CreateDashboard },
{ path: "/editashboard", name: "Edit Dashboard", component: EditDashboard },
{ path: "/dashboard", name: "Dashboard", component: Dashboard },
{ path: "/notauthorized", name: "Not Authorized", component: Notauthorized }
]
export const manager = [
{ path: "/createcalendar", name: "Create Calendar", component: CreateCalendar },
{ path: "/editcalendar", name: "Edit Calendar", component: EditCalendar },
{ path: "/dashboard", name: "Dashboard", component: Dashboard },
{ path: "/notauthorized", name: "Not Authorized", component: Notauthorized }
]
export const operator = [
{ path: "/dashboard", name: "Dashboard", component: Dashboard },
{ path: "/notauthorized", name: "Not Authorized", component: Notauthorized }
]
I will consider the option when we have known "keycloak" before react initialization (not async loading data for "keycloak"). You will be able to improve if you understand the idea
The main idea is to show all routes but almost all of them will be protected routes. See the example:
render (
<Switch>
<Route exact path="/login"> // public route
<LandingPageContainer />
</Route>
<AuthRoute exact path="/dashboard"> // for any authorized user
<Dashboard />
</AuthRoute>
<AdminRoute path="/create-user"> // only for admin route
<CreateUser />
</AdminRoute>
<AdminOrEngineerRoute path="/create-dashboard"> // only for admin or engineer route
<CreateDashboard />
</AdminOrEngineerRoute>
<Redirect to="/dashboard" /> // if not matched any route go to dashboard and if user not authorized dashboard will redirect to login
</Switch>
);
Then you can create list of components like this:
const AVAILABLED_ROLES = ['admin', 'engineer'];
const AdminOrEngineerRoute = ({ children, ...rest }) {
const role = keycloak && keycloak.token ? keycloak.idTokenParsed.preferred_username : '';
return (
<Route
{...rest}
render={({ location }) =>
AVAILABLED_ROLES.includes(role) && ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
As a result AdminOrEngineerRoute will allow to pass to this route only admin or engineer in other case you will get /login page
Always yours "IT's Bruise"

using of Custom Tabs with StackNavigator?

I have list of users , each user has its display image in the list.
What I am trying is whenever user presses the display image , get redirected to his/her profile through stackNavigation .
CustomTabView:
const CustomTabView = ({ router, navigation }) => {
const { routes, index } = navigation.state;
const ActiveScreen = router.getComponentForState(navigation.state);
const routeNav = addNavigationHelpers({
...navigation,
state: routes[index],
});
const routeOptions = router.getScreenOptions(routeNav, 'tabBar');
return (
<View style={styles.container}>
<CustomTabBar
navigation={navigation}
activeRouteName={routes[index].routeName}
icon={routeOptions.tabBarIcon}
/>
<ActiveScreen
navigation={addNavigationHelpers({
...navigation,
state: routes[index]
})}
/>
</View>
);
};
StackNavigator: // goToProfile.js // also tried placing in index.anndroid.js but didnt found a way to export
const goToProfile = StackNavigator({
Profile: {
screen: Profile,
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.person.name.first} ${navigation.state.params.person.name.last}`
})
},
})
custom tabs: //index.android.js
const CustomTabRouter = TabRouter(
{
Chat: {
screen: Chats,
path: ""
},
Status: {
screen: Contacts,
path: "notifications"
},
Camera: {
screen: Camera,
path: "settings"
}
},
{
initialRouteName: "Chat",
},
);
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabRouter)(CustomTabView)
);
Also my component :
<TouchableOpacity
onPress = { () => this.props.navigation.navigate('Profile', { item } ) } >
<Avatar
source={{ uri: item.picture.thumbnail }}
/>
</TouchableOpacity>
Your Stack Navigator will look like this:
const ChatStack= StackNavigator({
Chat:{screen: Chat,
navigationOptions: {
header: null,
}},
Profile: {
screen: Profile,
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.person.name.first} ${navigation.state.params.person.name.last}`,
tabBarVisible: false
// depending if you want to hide the tabBar on the profile page if not remove it.
})
},
})
Then Your customTab will take this shape:
const CustomTabRouter = TabRouter(
{
Chat: {
screen: ChatStack,
},
Status: {
screen: Contacts,
path: "notifications"
},
Camera: {
screen: Camera,
path: "settings"
}
},
.........
With those changes you should be able to navigate to Profile screen with your
this.props.navigation.navigate('Profile', { item } )
You want to dispatch a navigation action, not render a new stack - Although I'm not sure your navigators are properly constructed for this to succeed...
_goToProfile = (person) => {
<goToProfile navigate={this.props.navigation} />
}
should be
_goToProfile = (person) => {
this.props.navigation.navigate('Profile', person)
}

Resources