Search code examples
reactjspersistencereact-router-dom

Every page refresh while logged-in brought me back to dashboard (persistent login with protected route)


I've implement a simple protected route from Login & other protected components.

  • I'm using universal-cookie to persist user data/token
  • On protected route component, I make sure to always check whether the user exist or cookie exist, thus user is LOGIN. Otherwise, no. This is done with Context API

Protected Route for Login.js

  • this protected route is here to make sure if user cookie exist or user authenticate, then user should be redirect to Dashboard.js (home page for logged-in users)
  • ProtectedRouteLogin.js:-
import React, { useEffect } from 'react'
import { Redirect, Route } from 'react-router-dom'
import { useAuth } from '../../../contexts/Auth/AuthState'
import { isAuthenticated, setLoading } from '../../../contexts/Auth/AuthAction'

const ProtectedRoute\Login = ({ component: Component, ...rest }) => {
  const [authState, authDispatch] = useAuth()
  const { authenticated, loading } = authState

  // check if user is authenticated
  useEffect(() => {
    (async() => {
      await isAuthenticated(authDispatch)
      setLoading(authDispatch, false)
    })();
  }, [])

  return (
    <Route
      {...rest} render={
        props => {
          if(loading) return <div className="lds-hourglass"></div>
          if(!authenticated) return <Component {...props} />
          else return <Redirect exact to={{
            // here I redirect logged-in user to dashboard
            pathname: "/dashboard",
            state: { from: props.location }
          }} />
        }
      } 
    />
  )
}

export default ProtectedRouteLogin

Protected Route for Dashboard.js or other protected components

  • this exist for the same purpose as before, checking whether user is authenticated or not. If YES, then render the protected component. Otherwise, redirect to Login page
  • ProtectedRouteOthers.js:-
import React, { useEffect } from 'react'
import { Redirect, Route } from 'react-router-dom'
import { useAuth } from '../../../contexts/Auth/AuthState'
import { isAuthenticated, setLoading } from '../../../contexts/Auth/AuthAction'

const ProtectedRouteOthers = ({ component: Component, ...rest }) => {
  const [authState, authDispatch] = useAuth()
  const { authenticated, loading } = authState

  // check if user is authenticated
  useEffect(() => {
    (async() => {
      await isAuthenticated(authDispatch)
      setLoading(authDispatch, false)
    })();
  }, [])

  return (
    <Route 
      {...rest} render={
        props => {
          if(loading) return <div className="lds-hourglass"></div>
          if(authenticated) return <Component {...props} />
          else return <Redirect to={
            {
              // here redirect NOT logged-in user back to login page
              pathname: "/login",
              state: { from: props.location }
            }
          } />
        }
      } 
    />
  )
}

export default ProtectedRouteOthers

Issues facing

  • Since the checking (user aunthentication) is done in protected route layer or component just before rendering any protected routes, I'm having an issue where whenever I refreshed page (when logged-in). It keeps bring me back to Dashboard.js page.

  • I reckoned, this have something todo with having my authenticated state in Context API is false by default. Every time, I hit refreshed, the authenticated state will be false first in default state until I change/update it, after checking user cookie exist or not. (this is done in isAunthenticated function in Context API which was called in both Protected Routed above)

  • isAuthenticated function (check if user have cookie or not)

export const isAuthenticated = async(dispatch) => {
  setLoading(dispatch, true)

  // get user cookie
  let userExist = getCookie('user')

  /** Cookie Checking Style */
  if(userExist) {
    dispatch({
      type: 'SET_ISAUTHENTICATED',
      payload: true
    })
  } else {
    dispatch({
      type: 'SET_ISAUTHENTICATED',
      payload: false
    })
  }
}

So how can I alter my code so that, every time i refresh my protected page (when logged-in). I will stay on the refreshed page, instead of going back to the Dashboard page?


Solution

  • Luckily I found a solution. But I don't know whether this is an acceptable solution or not. At least this logic make sense to overcome my issue.

    I decided to add a logic before rendering Login.js page & other protected components in both Protected Route components (logic added in ProtectedRouteLogin.js & ProtectedRouteOthers)

    Logic added in ProtectedRouteLogin.js

    • here before rendering to Dashboard.js page, if user is authenticated, I check for props.location.pathname saved in cookie first. If present/exist, then render to said pathname. Otherwise, just render to Dashboard.js page
    • ProtectedRouteLogin.js:-
    import React, { useEffect } from 'react'
    import { Redirect, Route } from 'react-router-dom'
    import { useAuth } from '../../../contexts/Auth/AuthState'
    import { isAuthenticated, setLoading } from '../../../contexts/Auth/AuthAction'
    import { getCookie } from '../../../services/Cookie'
    
    const ProtectedRouteLogin = ({ component: Component, ...rest }) => {
      const [authState, authDispatch] = useAuth()
      const { authenticated, loading } = authState
    
      // check if user is authenticated
      useEffect(() => {
        (async() => {
          await isAuthenticated(authDispatch)
          setLoading(authDispatch, false)
        })();
      }, [])
    
      return (
        <Route
          {...rest} render={
            props => {
              if(loading) return <div className="lds-hourglass"></div>
              if(!authenticated) return <Component {...props} />
              else {
                // get pathname save in cookie
                let url = getCookie('onRefresh')
                
                return <Redirect exact to={{
                  // ternary function to determine redirection
                  pathname: url !== '' ? url : '/pfv4-admin/dashboard',
                  state: { from: props.location }
                }} />
              }
            }
          } 
        />
      )
    }
    
    export default ProtectedRouteLogin
    

    Logic added in ProtectedRouteOthers.js

    • here, before rendering to any protected components, I set cookie that gonna hold the props.location.pathname or url of about-to visit component. So that, I can cross-check this url in ProtectedRouteLogin.js above.
    • ProtectedRouteOthers.js:-
    import React, { useEffect } from 'react'
    import { Redirect, Route } from 'react-router-dom'
    import { useAuth } from '../../../contexts/Auth/AuthState'
    import { isAuthenticated, setLoading } from '../../../contexts/Auth/AuthAction'
    import { setCookie } from '../../../services/Cookie'
    
    const ProtectedRouteOthers = ({ component: Component, ...rest }) => {
      const [authState, authDispatch] = useAuth()
      const { authenticated, loading } = authState
    
      // check if user is authenticated
      useEffect(() => {
        (async() => {
          await isAuthenticated(authDispatch)
          setLoading(authDispatch, false)
        })();
      }, [])
    
      return (
        <Route 
          {...rest} render={
            props => {
              if(loading) return <div className="lds-hourglass"></div>
              if(authenticated) {
                // save last page seen address (url)
                setCookie('onRefresh', props.location.pathname, { path: '/' }) 
                return <Component {...props} />
              }
              else return <Redirect to={
                {
                  pathname: "/pfv4-admin",
                  state: { from: props.location }
                }
              } />
            }
          } 
        />
      )
    }
    
    export default ProtectedRouteOthers
    

    Feel free to add your own possible solution for this issue tho. Looking forward to try them.