Check user on every page using react and redux - reactjs

In my App.tsx file I have this setup
const App: React.FC = props => {
const [hasRendered, setHasRendered] = useState(false);
const dispatch = useDispatch();
const isAuth = authMiddleWare();
useEffect(() => setHasRendered(true), [hasRendered]);
if (!hasRendered && isAuth !== null) {
if (isAuth) {
dispatch(getUser());
} else {
dispatch(logoutUser());
}
}
...
<PrivateRoute path="/app" component={Dashboard} />
}
const PrivateRoute = ({ component, location, ...rest }: any) => {
const dispatch = useDispatch();
const authenticated = useSelector((state: RootState) => state.user.authenticated);
return <>{authenticated ? React.createElement(component, props) : <Redirect to={{ pathname: LoginRoute, state: { from: props.location } }} </>;
};
However, authentication only gets executed on browser reload, so data is not updated if I click a link within my app. I want the user to be checked when loading any private route.

Create a separate auth component and call it in every route and check the user is authenticated or not.
if the user authenticated then requested route show else return the 404page or homepage.
or check in every page you can check your authentication by putting this code in componentditmount section
import React, { Component } from 'react'
import { connect } from "react-redux";
import { userLoginAction } from "./store/actions/userLogin";
import * as actionType from "./store/actions/actionType";
import { Redirect } from 'react-router-dom';
class componentName extends Component {
constructor(props){
super(props)
this.state={
isMount:false
}
}
componentDidMount(){
const token = localStorage.getItem("token");
if (token != null && this.props.loginStatus === false) {
// this.userLoginData(token)
this.props.auth();
}
this.setState({ isMount: true });
}
render() {
return (
<>
{this.state.isMount?this.props.auth===false?<Redirect from ='/' to='/login' />:
<p>Hello World!</p> :<div>Loging.....</div>
}
</>
)
}
}
const mapGetState = (state) => {
return {
loginStatus: state.usrReducer.login_st,
auth: state.usrReducer.auth,
};
};
const mapDispatchState = (dispatch) => {
return {
login: (data) => dispatch({ type: actionType.LOGIN_ST, payload: data }),
auth: (data) => dispatch(userLoginAction(data)),
};
};
export default connect(mapGetState, mapDispatchState)(componentName)

Related

Handling user context on logout for user.isAdmin in ReactJs / Uncaught TypeError: user is null

I am trying to build a functionality in ReacJs (MERN) app, for my navbar link to '/admin',
to be only visible when user.isAdmin === true.
So my navbar (just an excerpt) looks the follwing:
Navbar
import { useAuthContext } from '../hooks/useAuthContext'
import { useIsAdmin } from '../hooks/useAdmin'
const Navbar = () => {
const [nav, setNav] = useState(false)
const {logout} = useLogout()
const {user} = useAuthContext()
const isAdmin = useIsAdmin()
//and on return
{ isAdmin ? <Link to='/admin'><li className='p-4 hover:text-[#00df9a] transition-all duration-500'>Admin {user.name}</li></Link> : <div></div> }
I as well made a function to get the user.isAdmin from AuthContext
useAdmin
import { useEffect, useState } from 'react';
import { useAuthContext} from '../hooks/useAuthContext';
export function useIsAdmin() {
const { user } = useAuthContext();
const [isAdmin, setIsAdmin] = useState(null);
useEffect(() => {
if (user) {
setIsAdmin(user.isAdmin && user);
}
}, [user]);
useEffect(() => {
if (!user || user === null) {
setIsAdmin(null);
}
}, [user]);
return isAdmin;
}
And this works okay, normal user does not see the /admin link, and user.isAdmin does.
However, the problem starts when I try to logout the user.isAdmin, then I receive "Uncaught TypeError: user is null"
as user changes back to Object { user: null }. On contrary,
I do not have that error, if I log out regular user, although it comes back to Object { user: null } as well.
I have tried working on my hook/function with no result, but I am guessing there is some problem with my authContext and global context for user.
So for reference my authContext file and logout.
Any hints and tips would be much appreciated.
AuthContext
import { createContext, useReducer, useEffect } from 'react'
export const AuthContext = createContext()
export const authReducer = (state, action) => {
switch (action.type) {
case 'LOGIN':
return { user: action.payload }
case 'LOGOUT':
window.localStorage.clear()
return {user: null}
case 'DELETE_USER':
return {...state, user: state.user.filter(u =>
u.id !== action.payload
)}
default:
return state
}
}
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
user: null
})
useEffect(() => {
const user = JSON.parse(localStorage.getItem('user'))
if (user) {
dispatch({ type: 'LOGIN', payload: user })
}
}, [])
console.log('AuthContext state:', state)
return (
<AuthContext.Provider value={{ ...state, dispatch }}>
{ children }
</AuthContext.Provider>
)
}
useLogout
import { useAuthContext } from './useAuthContext'
export const useLogout = () => {
const { dispatch } = useAuthContext()
const logout = () => {
// remove user from local storage
localStorage.removeItem('user')
// dispatch logout action
dispatch({ type: 'LOGOUT' })
}
return { logout }
}
Well, i forgot to add user to my if check in navbar. After this change, all works like a charm.
Navbar
{ isAdmin && user ? <Link to='/admin'><li className='p-4 hover:text-[#00df9a] transition-all duration-500'>Admin {user.name}</li></Link> : <div></div> }

React: Form submit must be clicked twice to set context globally using useContext

React Beginner here. I'm building a login form in React using jwt, axios and useContext. After being authorized from the backend, I store the data in the global context using AuthProvider and redirect to home page. the home page first checks for authorization and navigates to login on unauthorized access. However even after updating the auth (useState) on login, I still get a false value on the first click and get sent back to login even if authed. I've tried useEffects everywhere but to no avail. Code below
AuthProvider.jsx
import React, { useState } from "react";
import { createContext } from "react";
const AuthContext = createContext();
export default AuthContext
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [authed, setAuthed] = useState(false);
function login(user) {
setUser(user);
setAuthed(true);
}
return (
<AuthContext.Provider value={{login, user, authed}}>
{children}
</AuthContext.Provider>
)
}
ProtectedRoute.jsx to /home
import React, { useContext, useEffect } from "react";
import AuthContext from "../../context/AuthProvide";
const ProtectedRoute = ({children}) => {
const {login, user, authed} = useContext(AuthContext);
useEffect(() => {
alert("HELLO")
alert(authed)
if (!authed) {
return window.location.href = "/login";
} else {
return children
}
}, [authed, user, login]);
}
export default ProtectedRoute;
Top part of Login.jsx
import React, { useState, useEffect, useRef, useContext } from "react";
import "./login.css";
import AuthContext from "../../context/AuthProvide";
import { axios } from "../../context/axios";
const LOGIN = "/login";
const Login = () => {
const {login, user, authed} = useContext(AuthContext);
const [userEmail, setUserEmail] = useState("");
const [userPassword, setUserPassword] = useState("");
const [error, setError] = useState("");
const errorRef = useRef();
useEffect(() => {
}, [authed, user, login]);
useEffect(() => {
setError("");
}, [userEmail, userPassword]);
function handleUserEmail(event) {
setUserEmail(event.target.value);
}
function handleUserPassword(event) {
setUserPassword(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
axios.post(LOGIN, {
email: userEmail,
password: userPassword
}).then(response => {
if (response.data.error) {
setError(response.data.error);
} else {
// this is supposed to be the one to set the user and auth to true
login(response.data.token)
alert(authed)
window.location.href = "/";
}
}).catch(error => {
if (!error?.response) {
setError("NO SERVER RESPONSE");
} else if (error.response?.status === 400) {
setError("MISSING USER NAME OR PASSWORD");
} else if (error.response?.status === 401) {
setError("UNAUTHORIZED ACCESS");
} else {
setError("UNKNOWN ERROR");
}
errorRef.current.focus();
})
}
function resetForm() {
setUserEmail("");
setUserPassword("");
}
return (
// the form is here
)
Issues
The main issue I see with the code is the use of window.location.href. When this is used it reloads the page. This remounts the entire app and any React state will be lost/reset unless it is being persisted to localStorage and used to initialize app state.
It is more common to use the navigation tools from react-router-dom (I'm assuming this is the package being used, but the principle translates) to issue the imperative and declarative navigation actions.
Suggestions
The protected route component should either redirect to the login path or render the children prop if user is authorized. It passes the current location being accessed along in route state so user can be redirected back after successful authentication.
import { Navigate } from 'react-router-dom';
const ProtectedRoute = ({ children }) => {
const location = useLocation();
const { authed } = useContext(AuthContext);
if (!authed) {
return <Navigate to="/login" replace state={{ from: location }} />;
} else {
return children;
}
};
The Login component should use the useNavigate hook to use the navigate function to redirect user to protected route once authenticated.
import { useLocation, useNavigate } from 'react-router-dom';
const Login = () => {
const { state } = useLocation();
const navigate = useNavigate();
const { login, user, authed } = useContext(AuthContext);
...
function handleSubmit(event) {
event.preventDefault();
axios.post(
LOGIN,
{
email: userEmail,
password: userPassword
}
)
.then(response => {
if (response.data.error) {
setError(response.data.error);
} else {
login(response.data.token)
navigate(state?.from?.pathname ?? "/", { replace: true });
}
})
.catch(error => {
if (!error?.response) {
setError("NO SERVER RESPONSE");
} else if (error.response?.status === 400) {
setError("MISSING USER NAME OR PASSWORD");
} else if (error.response?.status === 401) {
setError("UNAUTHORIZED ACCESS");
} else {
setError("UNKNOWN ERROR");
}
errorRef.current.focus();
});
}
...
return (
// the form is here
);
}
Wrap the routes you want to protect with the ProtectedRoute component.
<AuthProvider>
<Routes>
...
<Route path="/login" element={<Login />} />
<Route
path="/test"
element={
<ProtectedRoute>
<h1>Protected Test Route</h1>
</ProtectedRoute>
}
/>
</Routes>
</AuthProvider>

How to prevent protected route from automatically redirecting on redux state change

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.
It seems the issue is that the loginAction action creator dispatches a login success action and the redux state is updated, which triggers a rerender. This rerender and updated authenticated state is used by the PublicRoute component and the user is bounced to the home route "/" before the redirect state update occurs in handleSubmit of the Login component.
As far as I can tell it appears you are using the redirect state in Login to pass a redirect prop to the View component to, I assume, render a Navigate component to redirect back to the referrer route.
This extra step/state update/rerender is completely extraneous and unnecessary. It is far more common to issue an imperative navigate from the login handler upon successful authentication. For this the navigate function is used to redirect back. Since the Login component is a class component it can't use the useNavigate hook. The options here are to either convert to function component or create a custom withNavigate HOC.
Example:
import { useNavigate } from 'react-router-dom';
const withNavigate = Component => props => {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};
export default withNavigate;
Decorate the Login component with the withNavigate HOC and access the navigate function from props and issue the imperative redirect.
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import Cookies from 'js-cookie';
import { loginAction } from '../state/action-creators/AuthAction';
import View from '../components/Login';
import withNavigate from '../path/to/withNavigate';
class Login extends React.Component {
constructor(props) {
super(props)
this.state = {
values: {
email_address: '',
password: '',
},
errors: {},
isSubmitting: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
...
async handleSubmit(event) {
if (event) {
event.preventDefault();
}
this.setState(
{ isSubmitting: true, errors: this.validation() },
async () => {
if (!Object.keys(this.state.errors).length) {
try {
const response = await this.props.login(this.state.values);
if (response) {
const redirect = Cookies.get('redirect');
this.props.navigate(redirect || "/", { replace: true });
}
} catch (error) {
console.log(error);
// didn't authenticate, clear submitting
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}
/>
)
}
}
const mapDispatchToProps = {
login: loginAction,
};
const mapStateToProps = (state) => ({
errorMessage: state.auth.errorMessage,
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withNavigate,
)(Login);

Using result of useSelector() in component

I am using react-redux to manage state and axios to make API calls.
My default state:
const defaultState = {
isLoading: true,
isAuthenticated: false,
authUser: {}
}
isLoading and authUser are both updated once the API's login call has ran successfully. Nothing wrong here.
In a userProfile component, I am using the following to get the logged in user's ID from the store:
const authUser = useSelector(state => state.authentication.authUser);
const user = users.getUser(authUser.id);
console.log(authUser);
authUser is always empty on page load but a split second late, it prints again, this time with the state contents.
I have the userProfile component behind a custom PrivateRoute component:
const PrivateRoute = ({ component: Component, ...rest }) => {
const authentication = useSelector(state => state.authentication);
return (
<Route
{...rest}
render={props =>
authentication.isAuthenticated ? (
<Component {...props} />
) : (
authentication.isLoading ? 'loading...' :
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
)
};
The loading... text shows before the component catches up, then the 2 console logs appear one after the other. The first empty, the second with data.
What am I missing here? This async stuff is driving me insane!
with this implementation of private route u can save current path of user and let user continue his process after login again
import React from 'react';
import { Login } from '../../pageComponents';
import { useSelector } from 'react-redux';
const PrivateRoute = (Component) => {
const Auth = (props) => {
const { isLoggedIn } = useSelector((state) => state.user);
// Login data added to props via redux-store (or use react context for example)
// If user is not logged in, return login componentc
if (!isLoggedIn) {
return <Login />;
}
// If user is logged in, return original component
return <Component {...props} />;
};
// Copy getInitial props so it will run as well
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default PrivateRoute;
then user it every page you want Private Rout like below
const myPrivateRoute = () => {
return (
<h1>Hello world</h1>
)
}
export default PrivateRoute(myPrivateRoute)
so create component like this and load your last section from local storage or cookie then validate and send into your redux store then you can memorize log
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from "axios";
import * as globalActions from "../../../store/actions/globalAction";
import * as userAction from "../../../store/actions/userAction";
const StorageManagment = () => {
const dispatch = useDispatch();
/* ----------------------------------- listening to token changes ----------------------------------- */
useEffect(() => {
dispatch(userAction.loadCart());
}, []);
useEffect(async () => {
try {
if (window)
await checkLocalDataWithRedux()
.then((data) => {
const { userDTO, token } = data;
dispatch(userAction.loginUser(token, userDTO));
})
.catch((e) => {
console.log(e);
dispatch(userAction.logOutUser());
});
} catch (e) {
throw e;
}
}, []);
function checkLocalDataWithRedux() {
return new Promise((resolve, reject) => {
try {
/* -------- read user data from localStorage and set into redux store ------- */
const { userDTO, token } = localStorage;
if (userDTO && token) {
resolve({ token: token, userDTO: JSON.parse(userDTO) });
} else logOutUser();
} catch (e) {
reject();
}
});
}
function logOutUser() {
dispatch(userAction.logOutUser());
}
return null;
};
export default StorageManagment;
then load it in your app.jsx file
app.js
render() {
return (
<React.Fragment>
<Provider store={store}>
<ErrorBoundary>
<StorageManagment />
<DefaultLayout>
<Application {...this.props} />
</DefaultLayout>
</ErrorBoundary>
</Provider>
</React.Fragment>
);
}

Private Route with Redux and react router

I have a simple app with auth and private route, I want to get data from the server if the user has token, the back end is ready and works fine, and I log in I have data in redux about the user, but I don't know how to handle with refresh page, where should I do dispatch to call action? if I do it in privateRoute.js, it works strange I want to call server ones, but I did it 3-4 times.
Here are my components without calling sessionActions, sessionActions should update loggedIn and the user would go to /login pages
PrivateRoute
import React, { useState, useEffect } from 'react';
import { Route, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import sessionAction from '../store/actions/sessionAction';
const PrivateRoute = ({ component: Component, ...rest }) => {
const { path, dispatch, loggedIn } = rest;
});
return (
<Route
path={path}
render={(props) => (loggedIn ? <Component {...props} />
<Component {...props}/>
: (<Redirect to="/login" />))}
/>
);
};
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
};
export default PrivateRoute;
sessionAction
const sessionAction = (path) => (dispatch) => {
return sessionServices(path)
.then((response) => {
console.log(response);
const { data } = response;
if (!data.error) {
dispatch(success(data));
}
dispatch(failure(data.error.text));
})
.catch((error) => error);
};
sessionService
import axios from 'axios';
axios.defaults.withCredentials = true;
const sessionServices = (path) => axios({
method: 'post',
url: `http://localhost:4000/api/pages${path}`,
})
.then((response) => response)
.catch((error) => console.log(error));
export default sessionServices;
You must dispatch the actions to fetch user data from the server in your App component which is the top-level component. Also, maintain a loading state in the reducer to render a Loader until the user data is fetched.
const App = props => {
useEffect(() {
this.props.sessionActions('/session')
}, []);
if(this.state.isLoading) return <Loader />;
const { loggedIn, user } = this.props;
return (
<Router>
{/* Your Private routes and other routes here */}
</Router>
)
}
const mapStateToProps = (state) => {
return {
isLoading: state.auth.isLoading,
user: state.auth.user,
loggedIn: state.auth.loggedIn
}
}
export default connect(mapStateToProps, { sessionAction })(App);

Resources