I use this method to show private pages to the user. The problem is that no page is displayed to me, but the logs that I put in PrivatePage work properly.
Components are correct because when I simply call the routes, the components are returned correctly but it has a problem with PrivatePage and does nothing.
Error Text:
Error: Objects are not valid as a React child (found: [object
Promise]). If you meant to render a collection of children, use an
array instead.
I must verify that user through the server before the user goes to an address because the user has a time limit to use the site, so I used Promise.
function Auth() {
return new Promise((resolve, reject) => {
let appData = GetAppData();
if (appData.UserToken.length == 0) {
reject(-2);
}
let request = {
Token: appData.UserToken,
UserId: appData.UserId
}
axios.post("****UserValidUrl****", request).then(response => {
switch (response.data.Type) {
case 10:
resolve(10);
break;
case -2:
reject(-2);
break;
case -13:
reject(-13);
}
}).catch(err => {
reject(-13);
})
})
}
PrivatePage:
const PrivatePage = ({children, ...rest}) => (
return <Route
{...rest}
render={async ({location }) => {
let type = 10;
try {
type = await Auth();
} catch(e) {
type = -13;
}
Log(type);
switch (type) {
case 10:
return children;
break
case -2:
return (
<Redirect
to={{
pathname: '/Auth/Login',
state: { from: location },
}}
/>)
case -13:
return (
<Redirect
to={{
pathname: '/NotFound',
state: { from: location },
}}
/>)
break;
}}
}/>
)
BasePage:
export default function BasePage() {
return (
<div>
<BrowserRouter>
<Switch>
<Route exact path={"/Auth/Login"} component={Login}/>
<PrivatePage path={"/"}>
<Switch>
<Route exact path={"/"} component={Home}/>
<Route exact path={"/Dashboard/Home"} component={Home}/>
<Route exact path={"/Dashboard/Profile"} component={Profile}/>
<Route component={NotFound}/>
</Switch>
}/>
</Switch>
</BrowserRouter>
</div>
);
}
Your navigation looks complex for authentication. you can treat your component as route and do routing as this.
Assuming that you have authenticated separately and you just check if it's good to go, instead of trying to authenticate every single time for route. If you prefer this authentication on route, still solution applies.
function AuthenticatedRoute({ children, ...props }) {
const isAuthenticated = useContext(AuthenticationContext);
return isAuthenticated ? (
<Route {...props}>{children}</Route>
) : (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
);
}
and use it like:
<AuthenticatedRoute path="/Dashboard/Home" exact>
<h1>Hey Doctor Strange! You made it</h1>
</AuthenticatedRoute>
I also have created a protected auth routing in this code sandbox for reference:
https://codesandbox.io/s/cranky-poincare-l2c06?file=/src/App.js:262-488
Maybe you can do something like this in your private route.
PrivatePage
const PrivatePage = ({ children, ...rest }) => (
<Route
{...rest}
render={async ({ location }) => {
let isAuthenticated = false;
try {
isAuthenticated = await Auth();
} catch(e) {
// If you want to log the error.
}
if (isAuthenticated) {
return children;
}
return (
<Redirect
to={{
pathname: '/login',
state: { from: location },
}}
/>
);
}}
/>
);
BasePage
export default function BasePage() {
return (
<div>
<BrowserRouter>
<Switch>
<Route exact path={"/Auth/Login"} component={Login}/>
<PrivatePage path={"/"}>
<Switch>
<Route exact path={"/"} component={Home}/>
<Route exact path={"/Dashboard/Home"} component={Home}/>
<Route exact path={"/Dashboard/Profile"} component={Profile}/>
<Route component={NotFound}/>
</Switch>
</PrivatePage>
</Switch>
</BrowserRouter>
</div>
);
}
EDIT
PrivatePage for specific numeric value in Auth.
const PrivatePage = ({children, ...rest}) => (
return <Route
{...rest}
render={async ({location }) => {
let authenticated = false;
try {
authenticated = await Auth();
} catch(e) {
// If you want to log the error.
}
if(authenticated === -2) {
// redirect one
} else if (authenticated === -13) {
// redirect two
} if (authenticated) {
return children;
}
return (
<Redirect
to={{
pathname: "/Auth/Login",
state: { from: location },
}}
/>
);
}}
/>
)
export default function PrivateRoute({component: Component, ...props}) {
let [PAGE_LOADING, SET_PAGE_LOADING] = useState(LOADING_TYPE.Loading);
let [AUTHENTICATION, SET_AUTHENTICATE] = useState(0);
useEffect(() => {
Auth().then((resolve) => {
SET_AUTHENTICATE(resolve);
}).catch((reject) => SET_AUTHENTICATE(reject))
.then(() => SET_PAGE_LOADING(LOADING_TYPE.Default))
}, [])
return (
PAGE_LOADING === LOADING_TYPE.Loading && AUTHENTICATION == 0 ?
<CtLabel/> :
<Route
{...props}
render={props => (
AUTHENTICATION == 10 ?
<Component {...props}/> :
<Redirect to={ROUTE_PATH.LOGIN}/>
)}/>
)
}
And for Used:
export default function BasePage() {
return (
<BrowserRouter>
<Switch>
{/*Other Routes*/}
<Private path={***} component={Home} exact/>
<Private path={***} component={Profile} exact/>
{/*Other Routes*/}
<Route component={NotFound}/>
</Switch>
</BrowserRouter>
);
}
Related
I'm working on an app with login and private routes, so far I have done the login and declared the private routes like this...
<Route path="/" render={() => <Redirect to="/app" />} />
<PrivateRoutes path="/app">
<AdminLayout>
<Switch>
<Route path="/app" exact render={() => <Redirect to="/app/dashboard" />} />
<Route path="/app/dashboard" component={Dashboard} />
</Switch>
</AdminLayout>
</PrivateRoutes>
<HomeLayout>
<Switch>
<Route path="/login" component={LogInPage} />
</Switch>
</HomeLayout>
</BrowserRouter>
then i create my private routes:
!!isAuthenticated ? (
<Route {...rest} render={() => children} />
) : (
<Redirect
to={{
pathname: '/login',
state: { from: history.location },
}}
/>
);
finally, i redirect to app/dashboard
if (isLogged) {
return <Redirect to="/app/dashboard" />;
}
return (
<form onSubmit={handleSubmit}>
so what is happening login works ok, it changes the URL but I don't see the components of the private route, I debugged it and it never declares these routes, if I keep the token and refresh I'm redirected as intended.
On the other hand a side question, im using hooks, how can I see that state: { from: history.location }, on the login component?
As Drew Reese commented my component was not updating this is why:
const PrivateRoutes = ({ children, ...rest }: Props) => {
useInjectReducer({ key: sliceKey, reducer: reducer });
useInjectSaga({ key: sliceKey, saga: authSaga });
const dispatch = useDispatch();
const history = useHistory();
const isAuthentificating = useSelector(selectIsAuthentificating);
const isAuthenticated = useSelector(selectIsAuthenticated);
useEffect(() => {
const token: string = cookies.get('token') || '';
dispatch(actions.getAccountRequest({ token }));
return () => {
dispatch(actions.reset());
};
}, [dispatch]);
return isAuthentificating ? (
I was getting the token inside the effect and that never happened I took it outside and it worked perfectly
For the other issue the state objected sended in the redirect is in the useLocation() hook
I am using aws-amplify, react-hook in my project. The app have some private Routes has been define below:
const ProtectedRoute = ({render: C, props: childProps, ...rest}) => {
return (
<Route
{...rest}
render={rProps =>
(childProps) ? (
<C {...rProps} {...childProps} />
) : (
<Redirect
to={`/login?redirect=${rProps.location.pathname}${
rProps.location.search
}`}
/>
)
}
/>
);
}
In App.js, we change childProps to define whether user is login or not. But when childProps change, Switch not re rendering. What is the way to force React re rendering its Route because isAuthenticated is change but ProtectedRoute is not rerender.
const [isAuthenticated, userHasAuthenticated] = useState(null);
useEffect(() => {
onLoad();
}, []);
async function onLoad() {
try {
let user = await Auth.currentSession();
if (user.accessToken.payload) {
userHasAuthenticated(user.accessToken.payload);
}
} catch (e) {
if (e !== 'No current user') {
alert(e);
}
}
}
.....
const childProps = isAuthenticated;
return (
<ApolloProvider client={client} >
<div className="App">
<BrowserRouter>
<Route path='/'>
<div>
<Switch>
<Route path='/login' render={props => <Login {...props}/>} exact/>
<ProtectedRoute
exact
path='/admin/:name'
render={()=> <Admin />}
props={childProps}
/>
<Route path='/' render={props => <User {...props} />}/>
</Switch>
</div>
</Route>
</BrowserRouter>
</div>
</ApolloProvider>)
The route only renders again when you enter that URL again. You are doing a Redirect, meaning it will never have a chance to enter the same URL after authentication is complete. You should delay rendering the protected route until you have confirmed authentication:
useEffect(() => {
async function onLoad() {
try {
let user = await Auth.currentSession();
userHasAuthenticated(!!user.accessToken.payload);
} catch (e) {
if (e !== 'No current user') {
alert(e);
}
}
}
onLoad();
}, []);
...
const ProtectedRoute = ({render: C, props: childProps, ...rest}) => {
if (childProps === null) {
// app still waiting authentication
return 'Loading...';
}
return (
<Route
{...rest}
render={rProps =>
(childProps) ? (
<C {...rProps} {...childProps} />
) : (
<Redirect
to={`/login?redirect=${rProps.location.pathname}${
rProps.location.search
}`}
/>
)
}
/>
);
}
I am creating a React app, that stores the users role (there are two possible roles 0, and 1 which are being used for conditional rendering) in a "global" React Context. The role gets assigned upon login. I am also using React Router to handle Routing, and wrote a ProtectedRoute component. My Problem is the following: When I am navigating via the NavBar all works perfectly fine, but when I enter e.g. /home into the url I get redirected to the LoginPage(which is the standard route when the role is not set to 0 or 1) and I can not access the other Routes anymore. However, the user does not get logged out (The session in the database is not being deleted), the app just seems to forget the global state "role", which is used to determine whether the user is allowed to access the individual routes. I am afraid my knowledge of the DOM and Router is too limited to solve this problem.
function App() {
return (
<AuthProvider>
<Router>
<NavigationBar />
<AuthContext.Consumer>
{context => (
<React.Fragment>
{!context.isAuthenticated() ? <Jumbotron/> : null}
</React.Fragment>
)}
</AuthContext.Consumer>
<Layout>
<Switch>
<Route exact path="/" component={() => <LandingPage />} />
<ProtectedRoute path="/home" component={Home} />
<ProtectedRoute path="/about" component={About}/>
<ProtectedRoute path="/account" component={Account} />
<ProtectedRoute path="/calender" component={Calender} />
<ProtectedRoute path="/xyz" component={Xyz} />
<ProtectedRoute path="/wasd" component={wasd} role={0} />
<Route component={NoMatch} />
</Switch>
</Layout>
</Router>
</AuthProvider>
);
}
export default App;
export const ProtectedRoute = ({ component: Component, ...rest }) => {
const { isAuthenticated, getRole } = useContext(AuthContext);
if (rest.role === 0) {
return (
<Route
{...rest}
render={props =>
getRole() === 0 ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/404",
state: {
from: props.location
}
}}
/>
)
}
/>
);
} else if (rest.role === 1) {
return (
<Route
{...rest}
render={props =>
getRole() === 1 ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/404",
state: {
from: props.location
}
}}
/>
)
}
/>
);
} else {
return (
<Route
{...rest}
render={props =>
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
)
}
/>
);
}
};
import React from "react";
const AuthContext = React.createContext();
export default AuthContext;
class AuthProvider extends Component {
constructor() {
super();
this.state = {
role: 2, //none=2
name: "",
email: ""
};
}
render() {
return (
<AuthContext.Provider
value={{
state: this.state,
isAuthenticated: () => {
if (this.state.role === 1 || this.state.role === 0) {
return true;
}
return false;
},
setRole: newRole =>
this.setState({
role: newRole
}),
getRole: () => {
return this.state.role;
},
setName: newName =>
this.setState({
name: newName
}),
getName: () => {
return this.state.name;
},
setEmail: newEmail =>
this.setState({
email: newEmail
}),
getEmail: () => {
return this.state.email;
},
}}
>
{this.props.children}
</AuthContext.Provider>
);
}
}
export default AuthProvider;
If you are entering the url directing into the browser, React will reload completely and you will lose all state whether 'global' or otherwise. The most likely scenario is that your router is trying to validate your ability to view a component before you have your auth data.
You don't include how you get your auth session from the database, but even if you refetch the auth session, there is going to be a period where your app has mounted, but you don't have the response yet. This will cause your protected route to believe you are unauthorized, and redirect to the fallback path.
Try adding a check either inside your protected route or before the router itself, that blocks rendering until your auth data is loaded. Although upon reading your question again, it seems like you may not be refetching your logged in user at all.
The title says it all.
When I type a url manually in a browser or try to refresh, it always goes back to root, is there any thing wrong? because I'm not getting it.
When I refresh or type url manually and I'm logged in it goes to the dashboard, and when I'm not logged in, it goes to login page.
Here is my code
import { Switch, Route, Redirect, Router } from 'react-router-dom';
<Provider store={this.props.store}>
<IntlProvider locale={this.props.locale} messages={this.props.localeData}>
<LocaleProvider locale={enUS}>
<Router history={history}>
<Switch>
<Route exact path="/" render={() => (<Redirect to="/dashboard" />)} />
<PrivateRoute path="/dashboard" component={App} locale={this.props.locale} redirectTo="/login" />
<PropsRoute path="/login" component={Login} />
<PropsRoute path="/reset-password" component={ResetPassword} />
<PropsRoute path="/loader" component={Loader} spinning={true} fullScreen={true} />
<Route component={NoMatch} />
</Switch>
</Router>
</LocaleProvider>
</IntlProvider>
</Provider>
this is my props route
const renderMergedProps = (component, ...rest) => {
const finalProps = Object.assign({}, ...rest);
return (
React.createElement(component, finalProps)
);
};
const PropsRoute = ({ component, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return renderMergedProps(component, routeProps, rest);
}} />
);
};
and my private route
const PrivateRoute = ({ user, component, redirectTo, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return user.logged ? (
renderMergedProps(component, routeProps, rest)
) : (
<Redirect to={{
pathname: redirectTo,
state: { from: routeProps.location }
}} />
);
}} />
);
};
Note: I'm also using Redux and Redux Saga.
const PrivateRoute = ({ user, component, redirectTo, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return user.logged ? (
renderMergedProps(component, routeProps, rest)
) : (
<Redirect to={{
pathname: redirectTo,
state: { from: routeProps.location }
}} />
);
}} />
);
};
Would say you should check if your user.logged hasn't change, it may be the case if your re-rendering the app when you manually change the url.
You could use localstorage to check if user is indeed logged or not.
I am using React routing v4 for a application that has a login and a home page once a dumb auth is done.
As of this point I have this LoadComponent.jsx in my index.js file:
class LoadComponent extends Component {
state = {
isLoggedOn: false,
};
onLoginCheck = (name, password) => {
console.log(name, password);
if (name && password) {
setTimeout(name, 100); // fake async
console.log('set the timeout');
}
this.setState({
isLoggedOn: true,
});
};
checkAuth = () => {
const { isLoggedOn } = this.state;
console.log('checking auth: ', isLoggedOn);
return (isLoggedOn);
};
render() {
return (
<BrowserRouter >
<Switch>
<Header isLogged={this.checkAuth()} />
<Route path="/login" render={props => <Login isLoggedOn={this.state.isLoggedOn} onLoggedInCheck={this.onLoginCheck} {...props} />} />
<PrivateRoute path="/" component={App} authenticated={this.state.isLoggedOn} />
</Switch>
</BrowserRouter>
);
}
}
My privateRouter looks like the following :
const PrivateRoute = ({
component, exact = false, path, authenticated,
}) => {
console.log('here : ', authenticated);
return (
<Route
exact={exact}
path={path}
render={props => (
authenticated ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
)}
/>
);
};
export default PrivateRoute;
The only thing that is rendered on the page is the Header component which makes sense, but the PrivateRoute component is not functioning since at first the Login component should be displaying. Im not sure what I am doing wrong here since I have followed the react router Redirect Auth example to some degree.