I have a react-native application whose top level App.js is as follows:
const AuthStack = createStackNavigator(
{ Landing, SignUpWithGoogle },
{
initialRouteName : 'Landing'
, navigationOptions : {
header : null
}
}
);
const AppStack = createStackNavigator(
{
AppContainer
, Campaign
, Compose
},
{
initialRouteName : 'AppContainer'
, navigationOptions : {
header : null
}
}
);
/**
#TODO:
force app to reload on user creation ...
there needs to be a state transition on user creation
... not sure how to
*/
class App extends React.Component {
render () {
const stack = (!this.props.authenticating && this.props.user)
? <AppStack {...this.props} />
: <AuthStack />
return (
<Loader
isLoaded = { !this.props.authenticating && this.props.feedIsLoaded}
imageSource = {logo}
backgroundStyle = {loading.loadingBackgroundStyle}
>
{stack}
</Loader>
)
}
}
There is a Loader component from https://github.com/TheSavior/react-native-mask-loader that hides the app with a splash page until the app has fetched user authentication data, or determined that this is a new user. This works great except in the case when the user signs up for the first time, then the app just jumps right into authenticated mode, and fails to load initial data that I want for the new user. The only to get around this is to restart the app, which is a no go. Is there a way to transition between AuthStack and AppStack better so that the information I want on signup are all there? One way would be to force reload the app so that we are directed back to the Loader screen and state, but it's unclear how this can be done.
I did something like this for my use case.
const Routes = {
Login: {
screen: Login
},
Status: {
screen: Status,
navigationOptions: {
gesturesEnabled: false, // To prevent swipeback
},
},
...
};
const LoggedRootStack = createStackNavigator(
{
...Routes
},
{
initialRouteName: 'Status'
}
)
const RootStack = createStackNavigator(
{
...Routes
},
{
initialRouteName: 'Login'
}
)
For passing props use Redux for keeping a global state. You can connect your components to redux state to access the props. Hope this helps
Related
I have a application where I have two logins one for superAdmin and other for 'Admin'.
I have several pages One which is common (home page) both users have excess to that.
then I have several other pages which are some for admin and others are for superAdmin.
Now when I open my page I am trying to go '/' this route (My home route).
What I am trying to do
Now If I am logged in as admin and admin user is typing some superAdmin url in address bar I want that to be redirect to current admin route
Same goes for superAdmin as well
both the user I want to restrict to excess each-others route
And if I am admin user or superAdmin user and trying to logged in and trying to excess authenticated route I should be redirect to home page
What I have done
I have created one component (Dynamic route) here I am checking what user is trying to do.
and in route.js in my routing file I am passing props as guest,superAdmin and admin
Dynamicroute.js code
I have created my context to store the user once they logged in
export default function Dynamicroute(props) {
const { user } = useAuthState(); // this I am getting from my context
console.log(user);
if (props.partner && !user) {
console.log('admin not logedin');
return <Redirect to="/admin" />;
} else if (props.admin && !user) {
console.log('superAdmin not loged in');
return <Redirect to="/superAdmin" />;
} else if (props.admin && user.role === 'admin') {
console.log('admin logedin');
return <Redirect to="/admin_home" />;
} else if (props.admin && user.role === 'superAdmin') {
console.log('super admin loged in');
return <Redirect to="/superadmin_home" />;
} else if (props.guest && user) {
console.log('guest');
return <Redirect to="/" />;
} else {
return <Route component={props.component} {...props} />;
}
}
My route.js
<DuynamicRoute exact path="/" component={Home} guest />
<DuynamicRoute path="/admin" component={loginAdmin} guest />
<DuynamicRoute path="/superAdmin" component={loginSuperAdmin} guest />
<DuynamicRoute path="/admin_home" component={admin_home} admin/>
<DuynamicRoute path="/superAdmin_home" component={superAdmin_home} superAdmin/>
Issue I am facing
I don't know what issue I am facing it is redirecting me to that route on login but content is not loading
If I console something on that page I am not able to get that, the page is getting blank.
I am following this lecture from 25:00 timing
Edited
Here is my code sand box
Please do check this
Edit
admin and super admin are going to be loged in different browsers, So just do not want admin to access super admin and vice-versa if they type in url each other's rout
For better management and development of the program along with the
best practices, Do the Authorization in React.js as follows:
Demo on Codesandbox
First: You need a class for checking permissions and routes/pages configs like below:
class AppUtils {
static setRoutes(config) {
let routes = [...config.routes];
if (config.auth) {
routes = routes.map((route) => {
let auth = config.auth ? [...config.auth] : null;
auth = route.auth ? [...auth, ...route.auth] : auth;
return {
...route,
auth
};
});
}
return [...routes];
}
static generateRoutesFromConfigs(configs) {
let allRoutes = [];
configs.forEach((config) => {
allRoutes = [...allRoutes, ...this.setRoutes(config)];
});
return allRoutes;
}
static hasPermission(authArr, userRole) {
/**
* If auth array is not defined
* Pass and allow
*/
if (authArr === null || authArr === undefined) {
// console.info("auth is null || undefined:", authArr);
return true;
} else if (authArr.length === 0) {
/**
* if auth array is empty means,
* allow only user role is guest (null or empty[])
*/
// console.info("auth is empty[]:", authArr);
return !userRole || userRole.length === 0;
} else {
/**
* Check if user has grants
*/
// console.info("auth arr:", authArr);
/*
Check if user role is array,
*/
if (userRole && Array.isArray(userRole)) {
return authArr.some((r) => userRole.indexOf(r) >= 0);
}
/*
Check if user role is string,
*/
return authArr.includes(userRole);
}
}
}
export default AppUtils;
Second: You need Authorization component for handling routes like below:
import React, { Component } from "react";
import AppUtils from "utils/AppUtils";
import { matchRoutes } from "react-router-config";
import { withRouter } from "react-router-dom";
import AppContext from "context/AppContext";
class AppAuthorization extends Component {
constructor(props, context) {
super(props);
const { routes } = context;
this.state = {
accessGranted: true,
routes
};
}
componentDidMount() {
if (!this.state.accessGranted) {
this.redirectRoute();
}
}
componentDidUpdate() {
if (!this.state.accessGranted) {
this.redirectRoute();
}
}
static getDerivedStateFromProps(props, state) {
const { location, userRole } = props;
const { pathname } = location;
const matched = matchRoutes(state.routes, pathname)[0];
return {
accessGranted: matched
? AppUtils.hasPermission(matched.route.auth, userRole)
: true
};
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.accessGranted !== this.state.accessGranted;
}
redirectRoute() {
const { location, userRole, history } = this.props;
const { pathname, state } = location;
const redirectUrl = state && state.redirectUrl ? state.redirectUrl : "/";
/*
User is guest
Redirect to Login Page
*/
if (!userRole || userRole.length === 0) {
history.push({
pathname: "/login",
state: { redirectUrl: pathname }
});
} else {
/*
User is member
User must be on unAuthorized page or just logged in
Redirect to dashboard or redirectUrl
*/
history.push({
pathname: redirectUrl
});
}
}
render() {
// console.info('App Authorization rendered', accessGranted);
return this.state.accessGranted ? (
<React.Fragment>{this.props.children}</React.Fragment>
) : null;
}
}
// AppAuthorization.defaultProps = {
// userRole: [] // You can manage roles by redux or any state managements
// };
AppAuthorization.contextType = AppContext;
export default withRouter(AppAuthorization);
Third: You need authRoles file or remote for managing roles on client like below:
/**
* Authorization Roles
*/
const authRoles = {
admin: ["admin"],
superAdmin: ["superAdmin"],
user: ["user"],
onlyGuest: []
};
export default authRoles;
Forth: If you want to move forward with this logic, you have to implement the structure of your pages as follows:
src
|---pages
|---home
|---HomePage.jsx
|---HomePageConfig.jsx
|
|- The rest of the pages should be implemented in the same way
For example: When you want to implement a page that only the admin can see (admin home page config):
import React from "react";
import authRoles from "auth/authRoles";
export const AdminHomePageConfig = {
auth: authRoles.admin,
routes: [
{
path: "/admin",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
Or the home page that everyone can see:
import React from "react";
export const HomePageConfig = {
routes: [
{
path: "/",
exact: true,
component: React.lazy(() => import("./HomePage"))
}
]
};
According to the example above, you can enter the auth prop with the role here, to restrict access to the page.
To get a closer look at this logic, I implemented it in the Codesandbox, which you can see via the link below:
Demo on Codesandbox
Notice: The above demo needs to be more complete, and instead of storing user roles in the state, it is better to use state management
packages (redux, ...) and also perform login operations through
cookies.
Instead of creating a dynamic route, you can create a function that checks auth and redirects on entering the route.
const yourRouter = () => {
// yourAuthLogic
const routeAuth = (Component, props) => {
// redirect logic
// here you use the if/else branching based on auth state to redirect
// if no redirect
return (
<Component {...props} />
)
}
return (
<Router>
<Switch>
<Route path="/admin" render={() => routeAuth(component, props)} />
<Route path="/superAdmin" render={() => routeAuth(component, props)} />
</Switch>
</Router>
)
}
The problem is that the DuynamicRoute component returns a Redirect in its top level, but Redirects don't work directly inside a Switch components. This is because a Redirect in a Switch would lead to an infinite redirection loop.
To fix this, you should return a top level Route from your custom Route component, while handling the redirection logic between the Route tags.
Also, it's worth mentioning that some routes shouldn't be special protected routes, but regular landing pages, such as the home and login pages.
Here is a sample project based on your CodeSandbox solution:
https://codesandbox.io/s/vigilant-feather-jbq4j
I made it so that superAdmin user can access admin level, but not the other way around. A lesser admin cannot access superAdmin content, without changing the active user to a superAdmin.
Here is an additional link with a slight modification to logic, for the use case where you would prefer that admin and superAdmin can't access each-other's protected pages: https://codesandbox.io/s/brave-haze-zsmn9?file=/src/ProtectedRoute.js
As this doesn't have an accepted answer yet I'll throw in the simple approach I use for adding / removing routes based on a condition.
The examples use Typescript but it should be relatively easy to strip the types out if needed.
Conditional Route Component:
This component has full typing of all the Route props plus an added prop called enabled.
When enabled is true the route will be rendered as normal, when it is false null will be returned.
// ConditionalRoute.tsx
import * as React from 'react';
import { Route, RouteProps } from 'react-router-dom';
interface ConditionalRouteProps extends RouteProps {
enabled?: boolean;
}
const ConditionalRoute: React.FunctionComponent<ConditionalRouteProps> = ({
enabled,
...routeProps
}) => {
if (!enabled) {
return null;
}
return (
<Route {...routeProps} />
);
};
export default ConditionalRoute;
Conditional Redirect Component:
This component has full typing of all the Redirect props plus an added prop called enabled.
When enabled is true the redirect will be actioned as normal, when it is false null will be returned.
// ConditionalRedirect.tsx
import * as React from 'react';
import { Redirect, RedirectProps } from 'react-router-dom';
interface ConditionalRedirectProps extends RedirectProps {
enabled?: boolean;
}
const ConditionalRedirect: React.FunctionComponent<ConditionalRedirectProps> = ({
enabled,
...redirectProps
}) => {
if (!enabled) {
return null;
}
return (
<Redirect {...redirectProps} />
);
};
export default ConditionalRedirect;
Using the Conditional Components:
Use the Conditional Routes / Redirects as you would their original base components except they will not come into effect unless the enabled property is true.
This also works with the Switch component as it will ignore components that return null.
// App.tsx
import * as React from 'react';
import { BrowserRouter, Switch } from 'react-router-dom';
interface AppProps {
authenticatedUser?: any;
}
const ConditionalRedirect: React.FunctionComponent<AppProps> = ({
authenticatedUser
}) => {
return (
<BrowserRouter>
<Switch>
{
// Always allow the unauthenticated error route
}
<Route
path="/error/401"
component={Unauthenticated}
/>
{
// If user is authenticated allow member route
}
<ConditionalRoute
enabled={!!authenticatedUser}
path="/member"
component={MemberHome}
/>
{
// If user is an admin allow admin route
}
<ConditionalRoute
enabled={authenticatedUser?.isAdmin}
path="/administration" component={AdminHome}
/>
{
// If user is an admin redirect uncaught route
// to administration route
}
<ConditionalRedirect
enabled={authenticatedUser?.isAdmin}
path="/"
to="/administration"
/>
{
// If user is authenticated redirect uncaught route
// to member route
}
<ConditionalRedirect
enabled={!!authenticatedUser}
path="/"
to="/member"
/>
{
// If user is not authenticated redirect uncaught
// route to unauthenticated error route
}
<ConditionalRedirect
enabled={!authenticatedUser}
path="/"
to="/error/401"
/>
</Switch>
</BrowserRouter>
);
};
export default App;
The main caveat of this solution is that any value in a condition has to be ready to be evaluated before rendering any of the routes. For example if you make an async request to check if a user is authenticated in the example App above, when the user is initially undefined before the async request returns it will redirect you to the 401 page.
I had to refactor recently the navigation inside my React Native App using React Navigation.
Indeed, I have several navigators inside my app for different purposes: screens when not connected, screens when connected and some screens that should be available in both situations
(e.g. terms of use).
So my question is the following, if I have this pattern, how can I for example navigate from TermsOfUse to Register without going back to Welcome?
I can't use navigate.goBack() or navigation.navigate('Register') since those screens are not in the same StackNavigator, and duplicate the TermsOfUse in both navigators would be quite dirty.
// Screens
const NotConnectedScreens = {
Welcome: { screen: WelcomeScreen },
Register: { screen: RegisterScreen },
}
const ConnectedScreens = {
Homepage: { screen: HomepageScreen },
Tutorial: { screen: TutorialScreen },
}
const OthersScreens = {
TermsOfUse: { screen: TermsOfUseScreen },
}
// Stacks
const NotConnectedStack = createStackNavigator(NotConnectedScreens, {
initialRouteName: 'Welcome',
})
const ConnectedScreens = createStackNavigator(ConnectedScreens, {
initialRouteName: 'Homepage',
})
const OtherStack = createStackNavigator(OtherScreens, {
initialRouteName: 'TermsOfUse',
})
// App navigation
const AppContainer = createAppContainer(
createSwitchNavigator(
{
NotConnectedStack: NotConnectedStack,
ConnectedStack: ConnectedStack,
OthersStack: OthersStack,
},
{
initialRouteName: 'LandingStack',
defaultNavigationOptions: {
header: null,
headerMode: 'none',
},
}
)
)
export { AppContainer as default }
I can't use navigate.goBack() or navigation.navigate('Register') since
those screens are not in the same StackNavigator.
Not completely true, if your screen has a unique name, wherever you need to navigate to that page you can call the function this.props.navigation.navigate('SecondPage').
but sometimes going to an screen from another stack can't be done because it needs some data to be passed when navigating to. in this case MAKE SURE YOUR SCREEN HAS A DIFFERENT NAME
DevicesList: {screen: DevicesList },// this is wrong
DevicesListPage: {screen: DevicesList },// this is right
then you can navigate to that page like below from another stack:
this.props.navigation.navigate('DevicesListPage', {
//your data
});
I am having an issue when I navigate from Home component that contains a list of companies and I press in a button to load the Company view, the data is being loaded but when I press back in Android is returning to Home and when I press in a different company button to load its details, is rendering the same view with the same previous data, that means, the component is not being updated/unmounted.
These are my routes
const drawerConfig = {
initialRouteName: 'Home',
contentComponent: SideMenu,
drawerWidth: width,
}
const MainDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: Home,
},
Company: {
screen: Company,
},
Gifts: {
screen: Gifts,
},
Contact: {
screen: Contact
}
},
drawerConfig,
);
const InitialStack = createStackNavigator(
{
Menu: {
screen: Menu,
path: 'menu/',
}
},
{
initialRouteName: 'Menu',
headerMode: 'none',
}
);
const SwitchNavigator = createSwitchNavigator(
{
Init: InitialStack,
App: MainDrawerNavigator,
},
{
initialRouteName: 'Init',
}
);
const AppContainer = createAppContainer(SwitchNavigator);
export default AppContainer;
I am navigating from Home to Company with this
goToCompany = company => event => {
this.props.navigation.navigate('Company', {
company,
});
}
And receiving the params in Company with this
componentWillMount() {
this.setState({ companyData: this.props.navigation.getParam('company', {}) });
}
So I am expecting the Company component will unmount on pop or allow me to change the state of the Company component when I send the details from Home.
I am using react-navigation 3.5.1 and react-native 0.59.3
React native navigation does not work as web. Read here for more details. https://reactnavigation.org/docs/en/navigation-lifecycle.html
When you navigat to other screen it doesn't actually get unmounted. Read documentation for details.
Use willFocus instead.
you can try using componentDidUpdate check in docs
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
You need to use the push method of navigation object. The push replace the current route with a new and the navigate search a route and if not exist create new one.
I am using TabNavigator from react-navigation. It's working fine but I need to do a little trick.
When I navigate between StackNavigator routes, after changing tabs I need my route go directly in the initial route. So I need the route state to be reset.
const HomeStack = StackNavigator({
Main: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
const AboutStack = StackNavigator({
Main: { screen: AboutScreen },
});
TabNavigator(
{
Home: { screen: HomeStack },
About: { screen: AboutStack },
}
Let's say I am in the Main route from the Home tab and then I have navigated to Profile before switching to the About tab. When I go back to the Home tab I want my app directly to navigate to the Main route and clear history. Just like a reset.
Any suggestion ?
You maybe could use the willFocus listener in the Profile route of your HomeStack.
Listener:
class Profile extends Component {
componentDidMount() {
this.didFocusListener = this.props.navigation.addListener(
'didFocus',
() => { console.log('did focus') },
);
}
componentWillUnmount() {
this.didFocusListener.remove();
}
render() {
return ( /* your render */ );
}
}
And then in the case you are on Main and you are navigating to your Profile route. You should set a params in the navigation to say that the previous route is Main.
Route parameters: navigation.navigate('Profile', { previous_screen: 'Main' });
So now in your willFocus listener:
if the previous_screen param is set, it means that you don"t have to do anything.
If not, means that you come from the other tab and that it will navigate to the wrong route. So you can either reset you're navigation route or just navigate to the 'Profile' route.
NOTE:
I didn't try this solution and maybe the transition animation is not going to be smooth. So tell me if it does the job well or not.
I have Question. How can I pass specific param from each screen to the StackNavigator header in order to come out different kind of components when reached the screen.
I have done some research about this kind of question, but there are not much info that can help me. So I posted here to find some help, hope there are someone who can guide me. Thanks a lot.
const mainNav = TabNavigator({
Home: {
screen: HomeScreen,
param:{
tabval:1
}
},
Live: {
screen: LiveScreen,
param:{
tabval:2
}
},
Radio: {
screen: RadioScreen,
param:{
tabval:3
}
},
} );
function DifferentComponents(tabval){
if(tabval == 1){
//do something
}else if(tabval == 2){
//do something
}else{
//do something
}
}
export const mainStack = StackNavigator({
Home: {
screen: mainNav,
navigationOptions: {
header: (
<View style={styles.topnavscrollview}>
<View style={{width:300}}>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
{this.DifferentComponents(tabval)} <-- Change this when switch to Live tab or others
</ScrollView>
</View>
</View>
),
},
},
Content: { screen: ContentScreen },
});
You can pass in the custom header value as a prop to the component.
Then put something like this at the top the component that you want to customize the header for:
class Home extends React.Component {
// dynamically set header title when component mounts
static navigationOptions = (props) => {
const title = props.myTitleForThisComponent;
return { title }
}
render(){
// render stuff..
}
}
When you navigate to the component via a StackNavigator link, any props that you pass into the component will be available as this.props.navigation.state.params.
For example, if you navigate to your component via StackNavigator like this:
this.props.navigation.navigate(
'Home',
/* below passes into the "Home" component as: this.props.navigation.state.params.title */
{ myCustomTitle: "hello there" }
)}
Then you can create a custom title for the Home page component like this:
static navigationOptions = ({ navigation }) => {
const { myCustomTitle } = navigation.state.params;
return { title: `${myCustomTitle} !!`}
}
hello there !!
Note: when you define your StackNavigator, you do not need to include the option navigationOptions.title, since you are add it dynamically when the component mounts.
However, you can provide generic navigationOptions values within the StackNavigator definition, to provide a "default" values for components that do not add/re-write them dynamically.