I have created two route components, one for authenticated users and the other for guest. each of the routes checks on the redux state for the authenticated property to determine if a user is logged in or not in order to make the appropriate redirection. Now, the issues is that the checking and redirection should only happen once when a user changes the route but instead each of the route components re-renders when the authenticated property changes on the redux state. This is affecting my login flow because the guest route components automatically handles the redirection instead of the login component.
Below is code:
App.js component:
import './App.css'
import { lazy, useEffect } from 'react'
import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'
import Layout from './components/Layout'
import AuthRoute from './components/AuthRoute'
import Error from './components/ErrorPage'
import configData from './config/configData.json'
import axios from 'axios'
// import { useEffect } from 'react'
// Import Css
import './assets/css/materialdesignicons.min.css'
import './Apps.scss'
import './assets/css/colors/default.css'
import SetupBusiness from './components/Business/SetupBusiness'
import Preview from './components/Business/Preview'
import About from './components/Business/About'
import Media from './components/Business/Media'
import { logoutAction } from './state/action-creators/AuthAction'
import { connect } from 'react-redux'
import PublicRoute from './components/PublicRoute'
const Register = lazy(() => import('./controller/Register'))
const Login = lazy(() => import('./controller/Login'))
function App(props) {
useEffect(() => {
async function checkUserAuth() {
const token = localStorage.getItem('token')
if (token) {
axios
.get(`${configData.SERVER_URL}/user/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then(() => {})
.catch((error) => {
console.log('nbvhg')
props.logout()
})
}
}
checkUserAuth()
setInterval(checkUserAuth, 15000)
}, [props])
return (
<div className="App">
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<h1>Homepage</h1>} />
<Route path="/" element={<PublicRoute />}>
<Route path="/register" element={<Register />} />
<Route path="/login" element={<Login />} />
</Route>
<Route path="/business/setup" element={<AuthRoute />}>
<Route element={<SetupBusiness />}>
<Route path="about" element={<About />} />
<Route path="media" element={<Media />} />
<Route path="preview" element={<Preview />} />
</Route>
</Route>
</Route>
<Route path="*" element={<Error />} />
</Routes>
</Router>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
return {
logout: () => {
return dispatch(logoutAction())
},
}
}
export default connect(null, mapDispatchToProps)(App)
PublicRoute component:
import { connect } from 'react-redux'
import { Navigate, Outlet } from 'react-router-dom'
import React from 'react'
class PublicRoute extends React.Component {
render() {
const { authenticated } = this.props
if (authenticated) {
return <Navigate to="/" />
}
return <Outlet />
}
}
const mapStateToProps = (state) => ({
authenticated: state.auth.authenticated,
})
export default connect(mapStateToProps, null)(PublicRoute)
Login component:
// React Basic and Bootstrap
import React from 'react'
import { connect } from 'react-redux'
import Cookies from 'js-cookie'
import { loginAction } from '../state/action-creators/AuthAction'
import View from '../components/Login'
class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
values: {
email_address: '',
password: '',
},
errors: {},
isSubmitting: false,
redirect: null,
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
let values = Object.assign({}, this.state.values)
let target = event.target
let name = target.name
let value = target.value
if (typeof values[name] !== 'undefined') {
if (name === 'checked') {
value = target.checked
}
values[name] = value
}
this.setState({ values })
}
validation() {
const errors = {}
let values = this.state.values
//email validation
if (!values.email_address.trim()) {
errors.email_address = 'Email is required'
} else if (!/\S+@\S+\.\S+/.test(values.email_address)) {
errors.email_address = 'Email is invalid'
}
//password validation
if (!values.password.trim()) {
errors.password = 'password is required'
} else if (values.password < 8) {
errors.password = 'PassWord need to be 8 characters or more'
}
return errors
}
async handleSubmit(event) {
if (event) {
event.preventDefault()
}
this.setState(
{ isSubmitting: true, errors: this.validation() },
async () => {
if (Object.keys(this.state.errors).length === 0) {
try {
const response = await this.props.login(this.state.values)
if (response) {
const redirect = Cookies.get('redirect')
if (redirect) {
this.setState({ redirect })
Cookies.remove('redirect')
} else {
this.setState({ redirect: '/' })
}
}
} catch (error) {
console.log(error)
} finally {
// this.setState({ isSubmitting: false })
}
} else {
this.setState({ isSubmitting: false })
}
}
)
}
render() {
return (
<View
onChange={this.handleChange}
values={this.state.values}
errors={this.state.errors}
errorMessage={this.props.errorMessage}
onSubmit={this.handleSubmit}
isSubmitting={this.state.isSubmitting}
redirect={this.state.redirect}
/>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
login: (values) => {
return dispatch(loginAction(values))
},
}
}
const mapStateToProps = (state) => {
return {
errorMessage: state.auth.errorMessage,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
AuthRoute component
import { logoutAction } from '../state/action-creators/AuthAction'
import { connect } from 'react-redux'
import { Navigate, Outlet } from 'react-router-dom'
import React from 'react'
import Cookies from 'js-cookie'
class AuthRoute extends React.Component {
render() {
const { authenticated, logout } = this.props
if (!authenticated) {
Cookies.set('redirect', window.location.pathname, { path: '/' })
logout()
return <Navigate to="/login" />
}
return <Outlet />
}
}
const mapDispatchToProps = (dispatch) => {
return {
logout: () => {
return dispatch(logoutAction())
},
}
}
const mapStateToProps = (state) => ({
authenticated: state.auth.authenticated,
})
export default connect(mapStateToProps, mapDispatchToProps)(AuthRoute)
Login action
import { formatError, login } from './AuthService'
export const CLEAR_ERROR_MESSAGE = '[register action] clear error message'
export const LOGIN_CONFIRMED_ACTION = '[login action] confirmed login'
export const LOGIN_FAILED_ACTION = '[login action] failed login'
export const LOGOUT_ACTION = '[logout action] logout action'
function clearErrorAction() {
return {
type: CLEAR_ERROR_MESSAGE,
}
}
function confirmedLoginAction(payload) {
return {
type: LOGIN_CONFIRMED_ACTION,
payload,
}
}
function failedLoginAction(message) {
return {
type: LOGIN_FAILED_ACTION,
payload: message,
}
}
export function loginAction({ email_address, password }) {
return (dispatch) => {
return login({
email_address,
password,
})
.then((response) => {
const token = response.data.access_token
localStorage.setItem('token', token)
dispatch(confirmedLoginAction(response))
return response
})
.catch((error) => {
if (!error.response) {
dispatch(failedLoginAction('Server error'))
} else {
const errorMessage = formatError(error.response.data)
dispatch(failedLoginAction(errorMessage))
}
throw error
})
}
}
function confirmedLogoutAction() {
return {
type: LOGOUT_ACTION,
}
}
export function logoutAction() {
return (dispatch) => {
localStorage.removeItem('token')
dispatch(confirmedLogoutAction())
}
}
AuthReducer
import {
LOGIN_CONFIRMED_ACTION,
LOGIN_FAILED_ACTION,
CLEAR_ERROR_MESSAGE,
LOGOUT_ACTION,
} from '../action-creators/AuthAction'
const initialState = {
user: {
first_name: '',
last_name: '',
user_name: '',
email_address: '',
password: '',
id: '',
is_archived: '',
projects: '',
date_created: '',
},
access_token: '',
errorMessage: '',
authenticated: !!localStorage.getItem('token'),
}
export function AuthReducer(state = initialState, action) {
if (action.type === CLEAR_ERROR_MESSAGE) {
return {
...state,
errorMessage: '',
}
}
if (action.type === LOGIN_CONFIRMED_ACTION) {
return {
...state,
authenticated: true,
user: action.payload,
}
}
if (action.type === LOGIN_FAILED_ACTION) {
return {
...state,
errorMessage: action.payload,
}
}
if (action.type === LOGOUT_ACTION) {
return {
...state,
authenticated: false,
user: {
first_name: '',
last_name: '',
user_name: '',
email_address: '',
password: '',
id: '',
is_archived: '',
projects: '',
date_created: '',
},
}
}
return state
}
I am getting error "react_devtools_backend.js:3973 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method." from react because I am trying to setState after login but the the guest route component have already redirected out from the Login component before setting the state hence the error.
Thank you in advance.