React render private route after redux(user authentication) is updated - reactjs

PrivateRoute is rendering before Redux gets current user data from the server. What is the best way to fix this issue?
The component looks like below. userAuth.isAuthenticated eventually updates to true but it renders <Redirect to="/login" /> first before it gets updated.
const userAuth = {
isAuthenticated: false
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
// PROBLEM: this renders before Redux gets user authentication data from the server
userAuth.isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
)}/>
)
class App extends Component {
componentDidMount() {
// Get current user data when user refresh the browser
// This sets isLoginSuccess: true is current user is logged-in in Redux store
this.props.getCurrentUserSession();
}
render() {
// Show loading icon while fetching current user data
if(this.props.user.isFetchingCurrentUserSession){
return (<div><img src={squareLogo} className="logo-loading" alt="Loading icon" /></div>);
}
// Set current user authentication status
userAuth.isAuthenticated = this.props.user.isLoginSuccess
return (
<Router>
<div>
<Route path="/public" component={Public} />
<PrivateRoute path="/private" component={Private} />
</div>
</Router>
);
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getCurrentUserSession
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(App)

You can do like this:
1.Intialize isAuthenticated to null
2.in render return use conditional rendering of the private component
const userAuth = {
isAuthenticated: null
}
in App render return:
return (
<Router>
<div>
<Route path="/public" component={Public} />
{(userAuth.isAuthenticated!==null)?<PrivateRoute path="/private"
component={Private} />:null}
</div>
</Router>
);

Related

Re render a children of Context Provider react

I have a function App in App.js as below
function App() {
return (
<AuthProvider>
<Layout>
<Routes>
{/* Basic routes access allowed by all */}
<Route exact path={RoutesNames.GLOBAL} element={<MainHomePage />} />
<Route exact path={RoutesNames.LOGIN} element={<Login />} />
<Route exact path={RoutesNames.REGISTER} element={<Register />} />
<Route exact path={RoutesNames.CATALOGUE} element={<Catalogue />} />
{/* Protected routes access allowed by user connected */}
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<ProtectedRoute />}>
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<RegistrationSuccess />} />
</Route>
{/* Errors routes */}
<Route path="*" element={<PageNotFound />} />
</Routes>
</Layout>
</AuthProvider>
)
}
The Layout contain the Header bar and the childrens after.
Now when I login from the login page, I'm redirected to Catalogue Page. And I have my AuthProvider who set the isAuth state to true as below :
import React, { Component, createContext } from 'react'
import VerificationUserAuth from '../../utils/VerificationUserAuth'
const AuthContext = createContext()
class AuthProvider extends Component {
constructor(props) {
super(props)
this.state = {
isAuth: false,
}
}
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
render() {
if (!this.state.isAuth) {
this.checkAuth()
}
return (
<AuthContext.Provider value={{ isAuth: this.state.isAuth }}>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer }
Now the problem is I check in my header who is envelopped by my AuthConsumer if he's authenticated or not and change the menu consequently. But each time I login, i need to refresh the page for Render the AuthProvider who check the authentification.
Btw there is my Header Bar :
export default function HeaderBar() {
return (
<AuthConsumer>
{({ isAuth }) => (
<header className="mb-5">
{isAuth ? (
<p>Connected</p>
) : (
<Navbar.Brand>
<Link to={RoutesNames.LOGIN}>
<Button className="btn btn-rounded btn-primary mr-2">Connexion</Button>
</Link>
<Link to={RoutesNames.REGISTER}>
<Button className="btn btn-rounded btn-normal">S'inscrire</Button>
</Link>
</Navbar.Brand>
)}
</Container>
</Navbar>
</header>
)}
</AuthConsumer>
)
}
So how can I re render my header after login part ? Thank's !
You need to change the isAuth state in a lifecycle component so that means whenever your app is loaded it will be initiated .
Put this function in a lifecycle method :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
componentDidMount(){
checkAuth()
}
In a functional component :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
React.useEffect(()=>{
checkAuth()
} , [])

Correct way to redirect user to different routes based on authentication in ReactJS

In my ReactJS app, routes are configured in below way:
class App extends React.Component{
constructor(props){
super(props);
this.state={
isLoggedin: false
}
}
componentDidMount(){
if(localStorage.getItem('name'))
{
this.setState({
isLoggedin: true
})}
}
render(){
return(
<>
<Switch>
<Route exact path="/" render={()=><Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/login" render={<Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/home" render={()=><Home isLoggedin={this.state.isLoggedin} />} />
</Switch></>
);
}
}
In Login.js:
class Login extends React.Component{
render(){
if(this.props.isLoggedin) return <Redirect to="/home" />;
return(
<h1>Login here</h1>
);
}
}
In Home.js:
class Home extends React.Component{
render(){
if(!this.props.isLoggedin) return <Redirect to="/login" />;
return(
<h1>Home</h1>
);
}
}
So what this code will do is that when the user visits the /, it would first go to Login component and as soon as isLoggedin is set to true, it would redirect the user to Home.js. Same thing would happen if user is not loggedin and he tries to access /home, he would be redirected to /login. Since I am using local storage, all of this would happen in flash of eye. It is also working just fine.
But I doubt if it is the best method to achieve my goal. I want to know if there is any more advisable method to do this.
Thanks!!
A more advisable method would be to decouple the auth checks from the components and abstract this into custom route components.
PrivateRoute - if the user is authenticated then a regular Route is rendered and the props are passed through, otherwise redirect to the "/login" path for user to authenticate.
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
AnonymousRoute - Basically the inverse of the private route. If the user is already authenticated then redirect them to the "/home" path, otherwise render a route and pass the route props through.
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
From here you render the Login and Home components into their respective custom route components.
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn} // *
path="/home"
component={Home}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn} // *
path={["/login", "/"]}
component={Login}
/>
</Switch>
* NOTE: The isLoggedIn={this.state.isLoggedIn} prop is only required here since the isLoggedIn state resides in the App component. A typical React application would store the auth state in a React Context or in global state like Redux, and thus wouldn't need to be explicitly passed via props, it could be accessed from within the custom route component.
Full sandbox code:
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
class Login extends Component {
render() {
return (
<>
<h1>Login here</h1>
<button type="button" onClick={this.props.login}>
Log in
</button>
</>
);
}
}
class Home extends Component {
render() {
return (
<>
<h1>Home</h1>
<button type="button" onClick={this.props.logout}>
Log out
</button>
</>
);
}
}
export default class App extends Component {
state = {
isLoggedIn: false
};
componentDidMount() {
if (localStorage.getItem("isLoggedIn")) {
this.setState({
isLoggedIn: true
});
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isLoggedIn !== this.state.isLoggedIn) {
localStorage.setItem("isLoggedIn", JSON.stringify(this.state.isLoggedIn));
}
}
logInHandler = () => this.setState({ isLoggedIn: true });
logOutHandler = () => this.setState({ isLoggedIn: false });
render() {
return (
<div className="App">
<div>Authenticated: {this.state.isLoggedIn ? "Yes" : "No"}</div>
<ul>
<li>
<Link to="/">/</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/login">log in</Link>
</li>
</ul>
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn}
path="/home"
render={() => <Home logout={this.logOutHandler} />}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn}
path={["/login", "/"]}
render={() => <Login login={this.logInHandler} />}
/>
</Switch>
</div>
);
}
}

React - Redirect user is not authenticated

I am using Firestore and have access to state that will inform me if the user is authenticated or not. I am mapping these conditions to props and will use react-router-dom's redirect to navigate user to the login page if not authenticated.
However, when I console log the authentication status i see that initially it will show false while it loads this information and then switched to true. However, by the time it sees that the users is authenticated, I have already redirected them.
How am I suppose to redirect users based on their authentication within react?
export class PrimaryNavBar extends Component {
render(){
const { auth, location } = this.props;
const authenticated = auth.isLoaded && !auth.isEmpty;
return (
<React.Fragment>
{!authenticated ? <Redirect to="/"/> : null}
<Navbar bg="primary" variant="dark">
<Container>
<Link to="/">
....
I have looked at other answers here but they don't really helped with redirects, just render nulls if not authenticated.
Update
I restructured my code to redirects will happen in the app.js file
The new problem is, whenever I user changes route, it will recheck auth which will be false at first and they end up back at the login screen.
class App extends Component {
state = {
isAuthenticated: this.props.isAuthenticated
}
render() {
const logoutHandler = () => {
this.props.firebase.logout();
};
const authenticated = this.props.auth.isLoaded && !this.props.auth.isEmpty;
return (
<Aux>
{!authenticated ? <Redirect to="/" /> : null}
<Route path="/" exact component={Login} />
<Route path='/(.+)' render={() => (
<React.Fragment>
<PrimaryNavBar logout={logoutHandler} />
<Switch>
<Route path="/dashboard" exact component={Dashboard} />
<Route path="/auctions" component={Auctions} />
<Route path="/auctions/:id" component={AuctionItem} />
<Route path="/auctions/create-auction" component={CreateAuction} />
<Route path="/bidders/create-bidder" component={CreateBidder} />
<Route path="/bidders/:id" component={Bidder} />
<Route path="/bidders" component={Bidders} />
</Switch>
</React.Fragment>
)} />
<ReduxToastr position="bottom-right" />
</Aux>
);
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.firebase.auth.uid,
auth: state.firebase.auth
};
};
const mapDisptachToProps = dispatch => {
return {};
};
export default withRouter(withFirebase(
connect(
mapStateToProps,
mapDisptachToProps
)(App)
));
You should probably wait untill auth.isLoaded is true? If thats the case, you could just do
const { auth, location } = this.props;
if (!auth.isLoaded) return null
This will refrain your nav component from rendering untill the authentication info is loaded. You might want to consider to implement this check (and the redirect) in a parent component level, though.

React route authentication

I am trying to create a server-side auth guard for react route. My flow is like... Whenever a route is hit on the front-end, a backend call is made to check if the user's session is present in the Database (since we store session in the database to keep track of sessions).
Here is my app component code:
export default function App() {
const routeComponents = routes.map(({ path, component }) => <AuthenticatedRoute exact path={path} component={component} props={'exact'} key={path} />);
return (
<div>
{window.location.origin === constant.PRODUCTION_DOMAIN_NAME && <Route
path="/"
render={({ location }) => {
if (typeof window.ga === 'function') {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview');
}
return null;
}}
/>}
<Switch>
<Route path="/login" component={LoginPage} />
{routeComponents}
</Switch>
<ToastContainer autoClose={constant.TOASTER_FACTOR} closeButton={false} hideProgressBar />
</div>
);
}
App.propTypes = {
component: PropTypes.any,
};
I have segregated the authenticated route into a separate class component
like:
export class AuthenticatedRoute extends React.Component {
componentWillMount() {
//I call backend here to check if user is authenticated by session Id in DB
}
render() {
const { component: Component, ...rest } = this.props;
return (
<Route exact {...rest} render={(props) => this.state.isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />} />
);
}
}
AuthenticatedRoute.propTypes = {
component: PropTypes.any,
....
};
const mapStateToProps = (state) => ({
reducer: state.get('myReducer'),
});
export default connect(mapStateToProps, { func })(AuthenticatedRoute);
But I face an issue where the login page is redirected twice. Could someone let me know a better approach for this?

apollo client query protected route

I am trying to apply protected route using private route method in my graphql react app. I am using apollo client 2.
The private route works as intended, that is it protects / prevents someone from manually entering url (in this case /surveys). However, when the page is reloaded or refreshed manually, graphql query will initially return an empty object data (when user is logged in) or undefined data. Because of this, the condition for redirect inside the private route is applied hence the client is redirected to the "/" path. Is there anyway around this? Here is my app and my private route code:
/* app.js */
....imports
const WHOAMI = gql`
query whoAmI {
whoAmI {
_id
email
}
}
`;
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Query query={WHOAMI}>
{({ loading, error, data }) => {
// console.log(data);
return (
<div className="container">
<div className="col s12 center-align">
<Switch>
<Route path="/surveys/new" component={SurveyNew} />
<PrivateRoute
path="/surveys"
userId={data.whoAmI}
component={SurveyList}
/>
<Route path="/" exact component={LandingPage} />
</Switch>
</div>
</div>
);
}}
</Query>
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
and my PrivateRouter file
...imports
const PrivateRoute = ({ component: Component, userId, ...rest }) => (
<Route {...rest} render={props => (
!userId || userId === 'undefined' ? (
<Redirect to={{
pathname: '/'
}}
/>
) : (
<Component {...props} />
)
)} />
);
export default PrivateRoute
I have probably the same architecture and I do
class App extends Component {
constructor (...args) {
super(...args)
this.state = {
user: {},
loading: true
}
}
componentDidMount() {
fetchUser()
.then(user => {
this.setState({
user,
loading: false
})
})
}
render () {
if (this.state.loading) {
return <LoadingPage />
} else {
return (
<ApolloProvider client={client}>
<BrowserRouter>
...
<BrowserRouter />
</ApolloProvider>
}
}

Resources