React-router, Switch and Redux: Route inexplicably not rendering, no error - reactjs

So, I'm trying to create a simple chat app with React, the app has an authentication workflow, and the logged in user is stored in a Redux store. I have a basic routing set up in my App.js (react-router v4), which ensures that only users that are logged in get to the actual chat screen. If the user is not logged in, they will be redirected to login screen. Now, I also want to persist the session (I'm using passport.js to handle session cookies in the backend), so that if the user that already logged in presses F5 and refresh the page for example, we won't have to go through the login process again. This is done by dispatching a thunk action (getLoggedUser()), that will simply check that the cookie we have in the browser is still accepted by the server, and then reload the user object to the Redux store (the prop loggedInUser).
This is where the weirdness ensues. My login and session authentication pipeline already works perfectly as it should, but when I refresh the page after logging in, the routes inside the router Switch do not render anything at all. I have tested that the right Switch actually gets rendered, but somehow the Route inside that switch will not get rendered. And this ONLY happens when refreshing after a successful login. I'm completely at loss as to what is causing this. I have tried using the withRouter middleware from react-router package, it did nothing. I even tried delaying the call to getLoggedUser() in the constructor to make sure that there was no race condition of some kind, but that didn't help either.
Here's the code for the App.js component:
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from 'react-router-dom';
import { connect } from 'react-redux';
import './App.css';
import MainChat from './components/MainChat';
import Login from './components/Login';
import { getLoggedUser } from './actions/';
class App extends Component {
constructor(props) {
super(props);
this.props.getLoggedUser();
}
render() {
return (
<Router>
<div className="App__container">
{this.props.loggedInUser ? (
<Switch>
<Route path="/chat" component={MainChat} />
<Redirect exact path="/" to="/chat" />
</Switch>
) : (
<Switch>
<Route path="/login" component={Login} />
<Redirect path="/" to="/login" />
</Switch>
)}
</div>
</Router>
);
}
}
const mapDispatchToProps = dispatch => ({
getLoggedUser: () => dispatch(getLoggedUser())
});
export default connect(
state => ({ loggedInUser: state.auth.loggedInUser }),
mapDispatchToProps
)(App);
Any ideas?

try puting your getLoggedUser() action trigger inside componentDidMount() function instead of your constructor as follow:
ComponentDidMount(){
this.props.getLoggedUser()
}
Hope it helps.

Related

Redirect in React Router 5 is successfully redirecting, but not rendering the child component

I have changed the routes in my app, and in case any users have bookmarked urls to the old routes I have added some redirects to the new routes. Most of them are working fine, however this one is not -
App.tsx
import { Router } from 'react-router-dom';
import Routes from './Routes';
import history from './history';
const App: FunctionComponent = () => (
<Router history={history}>
<Routes />
</Router>
);
export default App;
RouteSwitch.txs
import React, { FunctionComponent } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
const RouteSwitch: FunctionComponent = () => {
return (
<Switch>
<Redirect exact from="/documents" to="/documents/list" />
<Route exact path="/documents/list">
<DocumentsContainer />
</Route>
</Switch>
);
};
export default RouteSwitch;
The redirect from /documents to /documents/list works, however the DocumentsContainer does not get rendered. If I directly request /documents/list then it renders fine. It's as if <Switch> finds its first match (the Redirect) and then decides its job is done. I tried adding the push prop to the Redirect but it didn't make a difference.
My example is very similar to the one given on the React Training site - https://reacttraining.com/react-router/web/api/Redirect/from-string
Thoughts?
Problem solved - the DocumentsContainer component was actually rendering, but just not showing anything due to a quirk with how the use of the redirect was causing a loader count to be incremented by one (but not decremented) resulting in the loader count not returning to 0 to allow document data to be loaded from Redux and the content to be rendered.

Protecting routes in React app with React Router

I've created a simple React app with Redux, React Router and Auth0 which handles user authentications.
I'm trying to create this basic behavior to control access:
All unauthenticated users will automatically be sent to /public
Authenticated users can access all the other parts of the app
Once a user is authenticated by Auth0, I want to process the access_token and send user to / which is the Home component
Everything is "almost" working the way it should. The problem I'm having is that render() function in App.jsx is executing BEFORE the lock.on('authenticated') listener even has a chance to process the tokens returned by Auth0. As a result, the tokens are never stored and the user always seems to be unauthenticated. If I send user to /login, everything works fine because I'm not checking to see if the user is authenticated before rendering the Login component.
I think the way I'm handling protected routes needs to change. Any suggestions as to how to handle protected routes?
I'm providing the code that you need here. If you want to see the whole app, go to https://github.com/imsam67/react-redux-react-router-auth0-lock
The following is the App.jsx:
class App extends Component {
render() {
const isAuthed = isAuthenticated();
return (
<div>
<Switch>
<Route exact path="/" render={ props => isAuthed ? <Home {...props} /> : <Redirect to="/public" /> } />
<Route exact path="/login">
<Login />
</Route>
<Route path="/public">
<Public />
</Route>
</Switch>
</div>
);
}
}
This is the AuthWrapper component where I handle Auth0:
class AuthWrapper extends Component {
constructor(props) {
super(props);
this.onAuthenticated = this.onAuthenticated.bind(this);
this.lock = new Auth0Lock('my_auth0_client_id', 'my_domain.auth0.com', {
auth: {
audience: 'https://my_backend_api_url',
redirectUrl: 'http://localhost:3000/',
responseType: 'token id_token',
sso: false
}
});
this.onAuthenticated();
}
onAuthenticated() {
debugger; // After successful login, I hit this debugger
this.lock.on('authenticated', (authResult) => {
debugger; // But I never hit this debugger
let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
sessionStorage.setItem('access_token', authResult.accessToken);
sessionStorage.setItem('id_token', authResult.idToken);
sessionStorage.setItem('expires_at', expiresAt);
});
}
render() {
return(
<AuthContext.Provider value={{ lock: this.lock }}>
{this.props.children}
</AuthContext.Provider>
);
}
}
And here's index.js in case you need to see it:
import App from './components/App';
import AuthWrapper from './components/auth/AuthWrapper';
// Store
import appStore from './store/app-store';
const store = appStore();
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<AuthWrapper>
<App />
</AuthWrapper>
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
Here are the changes I made to make this work.
I now initialize Auth0 Lock in index.js to make it global
I moved the onAuthenticated() listener to LoginCallback.jsx component which is where the user is sent after a successful login. I believe moving the onAuthenticated() from App.jsx to LoginCallback.jsx made the biggest impact because the listener in LoginCallback.jsx executes before App.jsx.
On successful authentication, I also use this.props.history.push('/'); to send user to Home component
For full code, please see the repo at https://github.com/imsam67/react-redux-react-router-auth0-lock
Remember nothing is protected in the client side at all. If your concerned of routing to a component without auth just make sure no data is exposed(assuming they can't get any data without a token) and redirect if they landed there even after your router checks for auth or shows an error.
Remember nothing is protected in the client side at all. If your concerned of routing to a component without auth just make sure no data is exposed and redirect if they landed there even after your router checks for auth. I think #Sam has it right. The routes may not respond as expected to an asynchronous call changing it or may have odd behavior. I've never attempted a dynamic route this way but always had conditional renders of content components. A better approach may be to send the call and in the then block redirect to a url which the router knows to handle. Just not sure the router handles this very well. Catch the component checking for auth on load and redirect back to log on if not authorized. Sorry I'm not helping much here but conditional routes almost seem like an anti pattern but I guess it could work if we knew how the router renders its data after changes or if it actually does at all(the routes them selves.) So if they were to bookmark the url and try to return back that would be a 404 right? Maybe like a 401 unauthorized showing and redirect or link to log in might be better?
Dynamic routing need to be defined outside of the <Switch> scope. Here is an exemple assuming your function isAuthenticated() is a state (Redux or wathever)
import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
import { Router, Route, Switch, Redirect } from "react-router-dom";
// core components
import Admin from "layouts/Admin.js";
import SignIn from "layouts/SignIn";
const hist = createBrowserHistory();
const loggedRoutes = () => (
<Switch>
<Route path="/" component={SignIn} />
<Route path="/admin" component={Admin} />
<Redirect from="/admin" to="/admin/aboutUs/whatWeDo" />
</Switch>
);
const routes = () => (
<Switch>
<Route path="/" component={SignIn} />
<Route path="/login" component={Admin} />
<Redirect from="/" to="/login" />
</Switch>
);
ReactDOM.render(
<Router history={hist}>
{checkIfAuth? loggedRoutes():routes()}
</Router>,
document.getElementById("root")
);
In this exemple, If you are not login you are redirect to /login.

React Router Dom Private Route Always Redirects to Login

I’m building a full stack react application. I have the stack operating and pulling information from the backend when I’m calling it from the frontend. I’m using axios on the frontend to hit the endpoints I’ve set up on the backend. I’m having an issue with frontend authentication though. I’m using passport and passport-google-oauth20 to log users in with Google OAuth2.0. I have this working correctly. The problem I’m having: when a user hits a specific URL (controlled by react-router-dom) I have a function called authVerify run that pings the backend and checks if the user is logged in (by looking at the cookie - not accessible in JS). The function runs correctly and correctly updates the state. I have a state field for authenticated originally set to false, and on a successful 200 response I setState for authenticated to true. But, the protected routes I’ve built aren’t working. If the user is authenticated, they should be able to go to that route. If not, they get redirected to the login page. But, everything just redirects to the login page.
Here is the Router.js code:
import React from 'react';
import axios from 'axios';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import Login from './Login';
import SignUp from './SignUp';
import App from './App';
import NotFound from './NotFound';
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
};
}
componentDidMount() {
this.authVerify()
.then((res) => {
if (res.status === 200) {
console.log(this.state);
this.setState({
authenticated: true,
});
console.log(this.state);
}
})
.catch(err => console.log(err));
}
authVerify = () => axios.get('/authVerify')
render() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/" component={App} />
<Route exact path="/pages/signup" component={SignUp} />
<Route exact path="/pages/login" component={Login} />
<PrivateRoute path="/pages/dashboard" authenticated={this.state.authenticated} component={App} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
);
}
}
export default Router;
And here is the PrivateRoute.js code:
import React from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={props => (authenticated === true ? <Component {...props} /> : <Redirect to="/pages/login" />)}
/>
);
export default withRouter(PrivateRoute);
I have console logged the response from the API and I successfully get a 200 response. I’ve also console logged the state before and after the call to the API and before it’s false and after it’s true. But, I’m always redirected to the login page.
I think the issue is that the call to the API is taking too long and the component is mounting before authentication is verified. But, I thought by setting the state in the componentDidMount function would update the component and redirect appropriately. Any help would be appreciated.
I had the same problem when working with private routes, the issue is that your component is rendered before the resolution of your API call, the solution for me was to wait for the response to finish before rendering the component.
I used a isSendingRequest which starts as true and prevented my component from rendering (maybe you can display a spinner when this happens) after that when the request is finished, isSendingRequest is set to false and the component is rendered.
Hope it helps!
Please always add root path route "/" below of all other routes like:
<Route path="/checkout" component={YourComponent} />
.....
.....
<Route path="/" component={App} />

React router force to another page if a variable doesn't exist

Was wondering if react router has a way to preempt a route to check for a variable, e.g. session variable, and if that variable doesn't exist, to route to somewhere else or not at all? This question is tied into the browser history.
My scenario is this-- when a user logs in, there are certain routes exposed. If that user logs out, those routes are not exposed on the screen. However, since I have browser history, a logged out user is able to see the restricted routes by pressing the back button. Since I can not clear the browser history, I was thinking that if a restricted route gets called-- whether through router or through history--that there is a way to preempt the call and check if the user is logged in before allowing that route to proceed.
Any suggestions would be appreciated.
I handle this in my app with an AuthenticatedRoute component:
import React from 'react';
import { withRouter } from 'react-router'
import { Route, Redirect } from 'react-router-dom';
const AuthenticatedRoute = ({component: Component, ...rest}) => {
return session.VARIABLE
? <Route render={ props => <Component {...props} {...rest} />} />
: <Redirect to="/login" />;
};
export default withRouter(AuthenticatedRoute);
Usage:
<AuthenticatedRoute path="/path" component={MyComponent} />
If the variable exists, it returns a normal Route, but if it doesn't exist it returns a Redirect component which will immediately redirect the user to the given path.

a better pattern for authenticated React components and routes

I'm working on adding Auth0 authentication to my React app, and even though I have it working, I feel like there's a better way to approach this. I'm struggling to figure out a better pattern for the authentication logic.
The app setup is react + redux + react-router-redux + redux-saga + immutable + auth0-lock.
Beginning at the top, the App component defines the basic page layout, both Builder and Editor components require the user to be logged in, and authenticated() wraps each in a Higher Order Component responsible for handling authentication.
// index.js
import App from './containers/App';
import Builder from './containers/Builder';
import Editor from './containers/Editor';
import Home from './containers/Home';
import Login from './containers/Login';
import AuthContainer from './containers/Auth0/AuthContainer';
...
ReactDOM.render(
<Provider store={reduxStore}>
<Router history={syncedHistory}>
<Route path={'/'} component={App}>
<IndexRoute component={Home} />
<Route path={'login'} component={Login} />
<Route component={AuthContainer}>
<Route path={'builder'} component={Builder} />
<Route path={'editor'} component={Editor} />
</Route>
</Route>
<Redirect from={'*'} to={'/'} />
</Router>
</Provider>,
document.getElementById('app')
);
At the moment, AuthContainer doesn't do much except check the redux store for isLoggedIn. If isLoggedIn is false, the user is not allowed to view the component, and is redirected to /login.
// containers/Auth0/AuthContainer.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { redirectToLogin } from './Auth0Actions';
class AuthContainer extends React.Component {
componentWillMount() {
if (!this.props.isLoggedIn) {
this.props.actions.redirectToLogin();
}
}
render() {
if (!this.props.isLoggedIn) {
return null;
}
return this.props.children;
}
}
// mapStateToProps(), mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(AuthContainer);
The next piece is Auth0. The Auth0 Lock works in "redirect" mode, which means the user will leave the app to log in, and then be redirected back to the app at /login. As part of the redirect, Auth0 attaches a token as part of the URL, which needs to be parsed when the app loads.
const lock = new Auth0Lock(__AUTH0_CLIENT_ID__, __AUTH0_DOMAIN__, {
auth: {
redirect: true,
redirectUrl: `${window.location.origin}/login`,
responseType: 'token'
}
});
Since Auth0 will redirect to /login, the Login component also needs authentication logic. Similar to AuthContainer, it checks the redux store for isLoggedIn. If isLoggedIn is true, it redirects to the root /. If isLoggedIn is false, it'll attempt to authenticate.
// containers/Login/index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { authenticate, redirectToRoot } from '../Auth0/Auth0Actions';
class Login extends React.Component {
componentDidMount() {
if (!this.props.isLoggedIn) {
this.props.actions.authenticate();
}
else {
this.props.actions.redirectToRoot();
}
}
render() {
return (
<div>Login Page</div>
);
}
}
// mapStateToProps(), mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(Login);
With these pieces in place, my integration with Auth0 seems to be working. However, I now have AuthContainer and Login component, and they are very similar. I can't place the Login component as a child to AuthContainer since the login page does not actually require the user to be logged in.
Ideally, all authentication logic lives in one place, but I'm struggling to figure out another way to get it working, especially with the special case of the Auth0 redirect. I can't help but think that there must be a different approach, a better pattern for authentication flow in a react + redux app.
One thing that would be helpful is to better understand how to dispatch an async action on page load, before the app starts initializing. Since Auth0 works with callbacks, I'm forced to delay setting the redux initial state until after Auth0 invokes the registered callback. What is the recommended way to handle async actions on page load?
I've left out some pieces for brevity, like the actions and sagas, but I'll be more than happy to provide those if it'll be helpful.
May not be a complete answer, so sorry for that. Few things to address here:
Ideally, all authentication logic lives in one place
I'm not so sure this is ideal, depending on what you mean by "one place". There's noting wrong with having two functions that are similar but are different enough in some aspect that warrants a little repetition. From what I can see your code the logic is indeed slightly different so two components seems perfectly fine.
Instead of componentDidMount, use Route's onEnter prop
Putting your auth logic after component mounting will likely cause a flicker of your authenticated html showing before the auth logic can run. Conceptually, you would like to prevent rendering this component at all until the auth logic has run. Route's onEnter is perfect for this. https://github.com/ReactTraining/react-router/blob/master/docs/API.md#onenternextstate-replace-callback
let authenticate = (nextState, replace) => {
// check store details here, if not logged in, redirect
}
<Route path={'builder'} onEnter={authenticate} component={Builder} />
how to dispatch an async action on page load, before the app starts initializing
This is quite a common question for React Apps / SPAs. I think the best possible user experience is to display something right away, perhaps a loading spinner or something that says "Fetching user details" or whatnot. You can do this in your top level App container or even before your first call to ReactDOM.render
ReactDOM.render(<SplashLoader />, element)
authCall().then(data =>
ReactDOM.render(<App data={data} />, element)
).catch(err =>
ReactDOM.render(<Login />, element)
}
I'm doing the same thing in my project and working fine with redux, react-router, just have a look at my code below:
routes:
export default (
<div>
<Route path="/" component={AuthenticatedComponent}>
<Route path="user" component={User} />
<Route path="user/:id" component={UserDetail} />
</Route>
<Route path="/" component={notAuthenticatedComponent}>
<Route path="register" component={RegisterView} />
<Route path="login" component={LoginView} />
</Route>
</div>
);
AuthenticatedComponent:
export class AuthenticatedComponent extends React.Component {
constructor( props ) {
super( props );
}
componentWillMount() {
this.props.checkAuth().then( data => {
if ( data ) {
this.props.loginUserSuccess( data );
} else {
browserHistory.push( '/login' );
}
} );
}
render() {
return (
<div>
{ this.props.isAuthenticated && <div> { this.props.children } </div> }
</div>
);
}
}
notAuthenticatedComponent:
export class notAuthenticatedComponent extends React.Component {
constructor(props){
super(props);
}
componentWillMount(){
this.props.checkAuth().then((data) => {
if(data && (this.props.location.pathname == 'login')){
browserHistory.push('/home');
}
});
}
render(){
return (
<div>
{ this.props.children }
</div>
);
}
}
If you are following the Thanh Nguyen's answer use React's "Constructor" instead of "componentWillMount". As its the recommended way according to the docs.

Resources