I am trying to check if the user is logged in or not in Register.js component but I am getting undefined values of auth state in it. Please look into it if you can find where I am doing wrong. I have tried doing a lot of changes still I was not able to get it right.
Thank you for all of your help.
Ignore the context files
App.js
import { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import "./App.css";
import { Navbar } from "./components/layout/Navbar";
import { Home } from "./components/pages/Home";
import { MyList } from "./components/favorites/MyList";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
import { Provider } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/auth";
import { setAuthToken } from "./utils/setAuthToken";
if (localStorage.token) {
setAuthToken(localStorage.token);
}
function App() {
useEffect(() => {
store.dispatch(loadUser());
}, []);
return (
<Provider store={store}>
<>
<div>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/mylist' element={<MyList />} />
<Route path='/signin' element={<Login />} />
<Route path='/signup' element={<Register />} />
</Routes>
</div>
</>
</Provider>
);
}
export default App;
Register.js
import React, { useContext, useEffect, useState } from "react";
import { Link, Navigate, useNavigate } from "react-router-dom";
import { connect } from "react-redux";
import { register } from "../../actions/auth";
import axios from "axios";
const Register = ({ register, isAuthenticated, history, location, auth }) => {
const navigate = useNavigate();
const [user, setUser] = useState({
name: "",
email: "",
password: "",
password2: "",
});
const { name, email, password, password2 } = user;
const onFormDataChange = (e) => {
setUser({
...user,
[e.target.name]: e.target.value,
});
};
const onFormSubmit = async (e) => {
e.preventDefault();
register({ name, email, password });
};
console.log(auth);
console.log(isAuthenticated);
return (
<div>
<h1>Sign Up</h1>
<form action='' onSubmit={onFormSubmit}>
<div className='form-group'>
<label htmlFor='name'>Name</label>
<input
type='text'
name='name'
value={name}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='email'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password'>Password</label>
<input
type='password'
name='password'
value={password}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password2'>Password</label>
<input
type='password'
name='password2'
value={password2}
onChange={onFormDataChange}
/>
</div>
<input type='submit' value='Signup' />
<p>
Already have an account? <Link to='/signin'>Sign In</Link>
</p>
</form>
</div>
);
};
const mapStateToProps = (state) => {
const { isAuthenticated } = state.auth.isAuthenticated;
const { auth } = state.auth;
return {
isAuthenticated,
};
};
export default connect(mapStateToProps, { register })(Register);
index.js
import { combineReducers } from "redux";
import auth from "./auth";
export default combineReducers({
auth,
});
/auth/types.js
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const USER_LOADED = "USER_LOADED";
export const AUTH_ERROR = "AUTH_ERROR";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";
export const CLEAR_ERRORS = "CLEAR_ERRORS";
actions/auth.js
import axios from "axios";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "./types";
import { setAuthToken } from "../utils/setAuthToken";
export const loadUser = () => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (error) {
dispatch({
type: AUTH_ERROR,
});
}
};
export const register =
({ name, email, password }) =>
async (dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post("/api/users", body, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (error) {
dispatch({
type: REGISTER_FAIL,
});
}
};
export const login = (email, password) => async (dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post("/api/auth", body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (error) {
dispatch({
type: LOGIN_FAIL,
});
}
};
reducers/auth.js
/* eslint-disable default-case */
/* eslint-disable import/no-anonymous-default-export */
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "../actions/types";
const initalState = {
token: localStorage.getItem("token"),
isAuthenticated: false,
loading: true,
user: null,
};
export default function (state = initalState, action) {
const { type, payload } = action;
switch (type) {
case USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem("token", payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: true,
};
default:
return state;
}
}
store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const intialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
intialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
I might be wrong, but in you Register.js, in mapStateToProps, you doing following thing:
const { isAuthenticated } = state.auth.isAuthenticated;
const { auth } = state.auth;
It seems like it's not correct, unless your state has following structure:
{
auth: {
isAuthenticated: { isAuthenticated: *value here* },
auth: *value here*
}
}
I think, here might be mistake, and you should destructure values following way:
const { isAuthenticated } = state.auth;
const { auth } = state;
Related
i want to implement update user password form using react-router-dom v6 but this code is not working..
please please.. put your suggestion or explain me about my mistakes on this code.
userReducer.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_RESET,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const profileReducer = (state = {}, action) => {
switch (action.type) {
case UPDATE_PASSWORD_REQUEST:
return {
...state,
loading: true,
};
case UPDATE_PASSWORD_SUCCESS:
return {
...state,
loading: false,
isUpdated: action.payload,
};
case UPDATE_PASSWORD_FAIL:
return {
...state,
loading: false,
error: action.payload,
};
case UPDATE_PASSWORD_RESET:
return {
...state,
isUpdated: false,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
userAction.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const updatePassword = (passwords) => async (dispatch) => {
try {
dispatch({ type: UPDATE_PASSWORD_REQUEST });
const config = { headers: { "Content-Type": "application/json" } };
const { data } = await axios.put(
`/api/v1/password/update`,
passwords,
config
);
dispatch({ type: UPDATE_PASSWORD_SUCCESS, payload: data.success });
} catch (error) {
dispatch({
type: UPDATE_PASSWORD_FAIL,
payload: error.response.data.message,
});
}
};
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
store.js
import {createStore,combineReducers,applyMiddleware} from 'redux';
import thunk from "redux-thunk";
import {composeWithDevTools} from "redux-devtools-extension";
import { profileReducer } from './Reducers/userReducer';
const reducer = combineReducers({
profile:profileReducer,
})
let initialState = {};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
UpdatePassword.js
import React, { Fragment, useState, useEffect } from "react";
import "./UpdatePassword.css";
import Loader from "../Loader/Loader";
import { useDispatch, useSelector } from "react-redux";
import { clearErrors, updatePassword } from "../../Actions/userAction";
import { UPDATE_PASSWORD_RESET } from "../../Constants/userConstant";
import {useNavigate} from 'react-router-dom'
const UpdatePassword = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { error, isUpdated, loading } = useSelector((state) => state.profile);
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const updatePasswordSubmit = (e) => {
e.preventDefault();
const myForm = new FormData();
myForm.set("oldPassword", oldPassword);
myForm.set("newPassword", newPassword);
myForm.set("confirmPassword", confirmPassword);
dispatch(updatePassword(myForm));
};
useEffect(() => {
if (error) {
alert(error);
dispatch(clearErrors());
}
if (isUpdated) {
alert("Profile Updated Successfully");
navigate("/account");
dispatch({
type: UPDATE_PASSWORD_RESET,
});
}
}, [dispatch, error, isUpdated]);
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
{/* <MetaData title="Change Password" /> */}
<div className="updatePasswordContainer">
<div className="updatePasswordBox">
<h2 className="updatePasswordHeading">Update Profile</h2>
<form
className="updatePasswordForm"
onSubmit={updatePasswordSubmit}
>
<div className="loginPassword">
<input
type="password"
placeholder="Old Password"
required
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="New Password"
required
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="Confirm Password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<input
type="submit"
value="Change"
className="updatePasswordBtn"
/>
</form>
</div>
</div>
</Fragment>
)}
</Fragment>
);
};
export default UpdatePassword;
i want to make a form where user update user password.but due to any mistake this form is not working...
When I try the below code I get redirected to login page as if I'm not authenticated. Once I login I can't view the about page as it directs me to Welcome page because the logic in login page (if isAuthenticated navigates to Welcome page). If I remove the logic in login page I get stuck in login page only. Why I can't view about page?
PrivateOutlet.js ;
import React from 'react';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
if (isAuthenticated ) {
return <Outlet />
} else {
return <Navigate to='login' /> //Go to login
}
};
export default PrivateOutlet;
updated PrivateOutlet.js ;
import React from 'react';
import { connect } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(PrivateOutlet);
App.js
function App() {
return (
<Provider store={store}>
<Router>
<Layout>
<Routes>
<Route path='/' element={<WelcomePage/>} />
<Route path='/home' element={<Home/>} />
<Route element={<PrivateOutlet/>}>
<Route path='/about' element={<About/>} />
</Route>
<Route path='/contact' element={<Contact/>} />
<Route path='/login' element={<Login/>} />
<Route path='/signup' element={<Signup/>} />
<Route path='/reset-password' element={<ResetPassword/>} />
<Route path='/password/reset/confirm/:uid/:token' element={<ResetPasswordConfirm/>} />
<Route path='/activate/:uid/:token' element={<Activate/>} />
<Route path='*' element={<NotFound/>} />
</Routes>
</Layout>
</Router>
</Provider>
);
}
export default App;
login.js
import React, { useState } from 'react';
import { Link, Navigate } from 'react-router-dom';
import { connect } from 'react-redux';
import { Button } from '#mui/material';
import { login } from '../actions/auth';
import './Login.css';
import { Helmet } from 'react-helmet';
function Login({ login, isAuthenticated }) {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value});
const onSubmit = e => {
e.preventDefault();
login (email, password)
};
if (isAuthenticated) {
return <Navigate to='/' />
}
return (
<div className='login'>
<Helmet>
<title>Prosperity - Login</title>
<meta
name='description'
content='login page'
/>
</Helmet>
<h1 className='login__title'>Login</h1>
<p className='login__lead'>Login into your Account</p>
<form className='login__form' onSubmit={e => onSubmit(e)}>
<div className='login__form__group'>
<input
className='login__form__input'
type='email'
placeholder='Email *'
name='email'
value={email}
onChange={e => onChange(e)}
required
/>
</div>
<div className='login__form__group'>
<input
className='login__form__input'
type='password'
placeholder='Password *'
name='password'
value={password}
onChange={e => onChange(e)}
minLength='8'
required
/>
</div>
<Button className='login__button__main' type='submit'>Login</Button>
</form>
<p className='link__to__Signup'>
Do not have an account? <Link to='/signup' className='login__link'>Register</Link>
</p>
<p className='link__to__resetPassword'>
Forgot Password? <Link to='/reset-password' className='reset__password__link'>Reset Password</Link>
</p>
</div>
)
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect (mapStateToProps, { login }) (Login);
actions/Auth.js ;
import axios from 'axios';
import { setAlert } from './alert';
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from './types';
export const checkAuthenticated = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
const body = JSON.stringify({ token: localStorage.getItem('access') });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config)
if (res.data.code !== 'token_not_valid') {
dispatch({
type: AUTHENTICATED_SUCCESS
});
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} catch (err) {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
};
export const load_user = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
});
}catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
}catch (err) {
dispatch({
type: LOGIN_FAIL
});
dispatch(setAlert('Error Authenticating', 'error'));
}
};
export const signup = (name, email, password, re_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password, re_password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/`, body, config);
dispatch({
type: SIGNUP_SUCCESS,
payload: res.data
});
dispatch(setAlert('Check Your Email to Activate Your Account.', 'warning'));
} catch (err) {
dispatch({
type: SIGNUP_FAIL
})
}
};
export const verify = (uid, token) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token });
try {
await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/activation/`, body, config);
dispatch({
type: ACTIVATION_SUCCESS,
});
dispatch(setAlert('Account Activated Successfully.', 'success'));
} catch (err) {
dispatch({
type: ACTIVATION_FAIL
})
}
};
//Reset Password
export const reset_password = (email) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email });
try {
await axios.post (`${process.env.REACT_APP_API_URL}/auth/users/reset_password/`, body, config);
dispatch({
type: PASSWORD_RESET_SUCCESS
});
dispatch(setAlert('Check Your Email to Rest Password.', 'warning'));
} catch (err) {
dispatch({
type: PASSWORD_RESET_FAIL
});
}
};
// Reset Password Confirm
export const reset_password_confirm = (uid, token, new_password, re_new_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token, new_password, re_new_password });
try {
await axios.post (`${process.env.REACT_APP_API_URL}/auth/users/reset_password_confirm/`, body, config);
dispatch(setAlert('Password Rest Successful.', 'success'));
dispatch({
type: PASSWORD_RESET_CONFIRM_SUCCESS
});
} catch (err) {
dispatch({
type: PASSWORD_RESET_CONFIRM_FAIL
});
}
};
//Logout
export const logout = () => dispatch => {
dispatch(setAlert('Logout successful.', 'success'));
dispatch({
type: LOGOUT
});
};
reducers/Auth.js ;
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
SIGNUP_SUCCESS,
SIGNUP_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_SUCCESS,
AUTHENTICATED_FAIL,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAIL,
PASSWORD_RESET_CONFIRM_SUCCESS,
PASSWORD_RESET_CONFIRM_FAIL,
LOGOUT
} from '../actions/types';
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch(type) {
case AUTHENTICATED_SUCCESS:
return {
...state,
isAuthenticated: true
}
case LOGIN_SUCCESS:
localStorage.setItem('access', payload.access);
localStorage.setItem('refresh', payload.refresh);
return {
...state,
isAuthenticated: true,
access: payload.access,
refresh: payload.refresh,
}
case USER_LOADED_SUCCESS:
return {
...state,
user: payload
}
case SIGNUP_SUCCESS:
return {
...state,
isAuthenticated: false,
}
case AUTHENTICATED_FAIL:
return {
...state,
isAuthenticated: false
}
case USER_LOADED_FAIL:
return {
...state,
user: null
}
case LOGIN_FAIL:
case SIGNUP_FAIL:
case LOGOUT:
localStorage.removeItem('access');
localStorage.removeItem('refresh');
return {
...state,
access: null,
refresh: null,
isAuthenticated: false,
user: null,
}
case PASSWORD_RESET_SUCCESS:
case PASSWORD_RESET_FAIL:
case ACTIVATION_SUCCESS:
case ACTIVATION_FAIL:
case PASSWORD_RESET_CONFIRM_SUCCESS:
case PASSWORD_RESET_CONFIRM_FAIL:
return {
...state
}
default:
return state
}
};
isAuthenticated isn't passed as a prop to PrivateOutlet.
<Route element={<PrivateOutlet />}> // <-- no isAuthenticated prop
<Route path='/about' element={<About />} />
</Route>
isAuthenticated is stored in redux state and the initial value is null and not the true|false after a successful or failed authentication attempt.
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null,
};
You can explicitly check if the isAuthenticated state is null and conditionally return null or a loading indicator, etc... while the authentication status is being resolved. Once the authentication status resolves to a non-null value then either the routed component or redirect can be rendered.
import React from 'react';
import { connect } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = ({ isAuthenticated }) => {
if (isAuthenticated === null) {
return null;
}
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
const mapStateToProps = state => ({
isAuthenticated: state => state.auth.isAuthenticated,
});
export default connect(mapStateToProps)(PrivateOutlet);
or
import React from 'react';
import { useSelector } from 'react-redux';
import { Outlet, Navigate } from 'react-router-dom';
const PrivateOutlet = () => {
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
if (isAuthenticated === null) {
return null;
}
return isAuthenticated ? <Outlet /> : <Navigate to='/login' replace />;
};
export default PrivateOutlet;
Update
If you want to redirect a user back to the page they were originally trying to access the PrivateOutlet component should grab the current location and pass this in route state to the login page.
import { Outlet, Navigate, useLocation } from 'react-router-dom';
const PrivateOutlet = () => {
const location = useLocation();
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
if (isAuthenticated === null) {
return null;
}
return isAuthenticated
? <Outlet />
: <Navigate to='/login' state={{ from: location }} replace />;
};
Then the Login component grabs this value from route state to imperatively navigate back to the original route.
const navigate = useNavigate();
const { state } = useLocation();
const { from } = state || {};
...
navigate(from.pathname || "/home", { state: from.state, replace: true });
Example
import React, { useState } from 'react';
import { Link, Navigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const { state } = useLocation();
const { from } = state || {};
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return (
<Navigate
to={from.pathname || "/home"}
replace
state={from.state}
/>
);
}
return (
...
);
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
Update 2
I get on the console; Login.js:30 Uncaught TypeError: Cannot read properties of undefined (reading 'pathname')
I think what is occurring here is that the login action updates your redux store, which should trigger the component to rerender, and I suspect it's this rerender that looses the route state. Route state is pretty transient and only exists during the transition and render cycle when it's received. You could probably use a React ref to cache a copy of the route state to use later.
Example:
import React, { useRef, useState } from 'react';
import { Link, Navigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const { state } = useLocation();
const { from } = state || {};
const fromRef = useRef(from);
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return (
<Navigate
to={fromRef.current.pathname || "/home"}
replace
state={fromRef.current.state}
/>
);
}
return (
...
);
};
It may be more practical to pass an "onLoginSuccess" handler to the login action, and issue an imperative navigation from the asynchronous action.
Example:
import React, { useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
...
function Login({ login, isAuthenticated }) {
const navigate = useNavigate();
const { state } = useLocation();
const { from } = state || {};
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
const onSuccess = () => {
navigate(
from.pathname || "/home",
{
replace: true,
state: from.state
}
);
};
login(email, password, onSuccess);
};
return (
...
);
};
...
export const login = (email, password, onSuccess) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(
`${process.env.REACT_APP_API_URL}/auth/jwt/create/`,
body,
config
);
dispatch({ type: LOGIN_SUCCESS, payload: res.data });
dispatch(setAlert('Authenticated successfully', 'success'));
dispatch(load_user());
if (onSuccess) {
onSuccess();
}
} catch (err) {
dispatch({ type: LOGIN_FAIL });
dispatch(setAlert('Error Authenticating', 'error'));
}
};
Could someone please let me know why the state isn't being updated from the reducer? The useEffect(()=>{}) isn't being triggered when the state is being returned from the reducer. I have validated the correct information is being passed to the return, but nothing can be seen from the LoginScreen.
Context Script
import React, { createContext, useReducer } from "react";
import userReducer from "./UserReducer";
export const UserContext = createContext();
const initialState = {
userData: [],
isLoggedIn: false,
isAdmin: false,
isEmployee: false,
errorMessage: [{ success: false, statusCode: 0, error: null }],
};
const UserContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
const registerUser = (user) =>
dispatch({ type: "REGISTER_USER", payload: user });
const loginUser = (user) => dispatch({ type: "LOGIN_USER", payload: user });
const deleteUser = (user) => dispatch({ type: "DELETE_USER", payload: user });
const updateUser = (user) => dispatch({ type: "UPDATE_USER", payload: user });
const contextValues = {
...state,
registerUser,
loginUser,
deleteUser,
updateUser,
};
return (
<UserContext.Provider value={contextValues}>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
Reducer Script
import axios from "axios";
axios.defaults.withCredentials = true;
const userReducer = (state = {}, action) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
switch (action.type) {
case "REGISTER_USER":
break;
case "LOGIN_USER":
console.log(state);
const email = action.payload.email;
const password = action.payload.password;
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
// localStorage.setItem("authToken", response.data.authToken);
state.userData = response.data.user;
state.isLoggedIn = true;
if (response.data.user.role === 9) {
state.isAdmin = true;
state.isEmployee = true;
} else {
state.isAdmin = false;
state.isEmployee = false;
}
}
})
.catch((error) => {
state.errorMessage = {
success: error.response.data.success,
statusCode: error.response.status,
message: error.response.data.error,
};
});
return {
...state,
userData: [state.userData],
isLoggedIn: state.isLoggedIn,
isAdmin: state.isAdmin,
isEmployee: state.isEmployee,
errorMessage: [state.errorMessage],
};
default:
return state;
}
};
export default userReducer;
Login Form
import { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import {
Button,
Form,
Grid,
Message,
Segment,
Image,
Container,
} from "semantic-ui-react";
//Custom Imports
import "./LoginScreen.css";
import Logo from "../../../img/logo.png";
//Context
import { UserContext } from "../../context/UserContext";
const LoginScreen = ({ history }) => {
const { userData, loginUser, isLoggedIn, errorMessage, clearErrorMessage } =
useContext(UserContext);
const [user, setUser] = useState({ email: "", password: "" });
const [error, setError] = useState("");
useEffect(() => {
console.log(errorMessage);
if (localStorage.getItem("authToken")) {
history.push("/dashboard");
}
}, [history]);
useEffect(() => {
if (isLoggedIn) {
console.log(userData);
console.log("User is Logged in");
// history.push("/");
}
if (!errorMessage.success && errorMessage.error != null) {
console.log(errorMessage);
setError(errorMessage.message);
setTimeout(() => {
setError("");
}, 5000);
}
}, [userData, errorMessage, isLoggedIn]);
return (
<Container className="login-container">
<Grid
textAlign="center"
style={{ height: "100vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Image src={Logo} className="login-logo" />
<Form size="large" onSubmit={() => loginUser(user)}>
<Segment stacked>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email Address"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="Password"
value={user.password}
type="password"
onChange={(e) => setUser({ ...user, password: e.target.value })}
/>
{error && <span>{error}</span>}
<Button color="blue" fluid size="large" type="submit">
Login
</Button>
</Segment>
</Form>
<Message>
Don't have an account? <Link to="/register">Sign Up</Link>
</Message>
</Grid.Column>
</Grid>
</Container>
);
};
export default LoginScreen;
Refactor your login function like this
const loginUser({ email, password }) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
dispatch({ type: 'LOGIN_SUCCESS', payload: response.data });
}
})
.catch((error) => {
dispatch({ type: 'LOGIN_FAILED', payload: error });
});
}
and then your reducer
...
switch(action.type) {
...
case 'LOGIN_SUCCESS':
// return here a new object
// do not mutate the state (state.something = something) is not allowed
...
case 'LOGIN_FAILED':
// handle error
}
Prerequisite Reducer Concepts
Redux and useReducer use reducer like (previousState, action) => newState.
The reducer should be a 'pure' function as in this document. The promises, api calls should not be use inside reducers.
The problem:
Because you call api/promise inside the reducer. The reducer function returns the value before the promise finish. So when the promise finishes, nothing happen.
// A will be return before B, C are going to call
case "LOGIN_USER":
promiseFn()
.then(/* B */ ...)
.catch(/* C */ ...)
// A
return {
...
}
Solution:
Separate the non-pure calls from the reducer. And put them in the other code blocks (like inside hooks, event handlers...).
I'm pretty new to Redux, but am trying to add it to my existing project and am getting an infinite refresh loop back to my /login page. I don't see an immediate error and can't locate where the issue might be coming from. I think the loop might be coming from the render in App.js that pulls in the Login component, but can't seem to pinpoint it. I'd really appreciate any help with this one!
App.js:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import MuiThemeProvider from '#material-ui/core/styles/MuiThemeProvider';
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
import themeFile from './util/theme';
import jwtDecode from 'jwt-decode';
// Redux
import { Provider } from 'react-redux';
import store from './redux/store';
// Components
import Navbar from './components/Navbar';
import AuthRoute from './util/AuthRoute';
// Pages
import home from './pages/home';
import login from './pages/login';
import signup from './pages/signup';
const theme = createMuiTheme(themeFile);
let authenticated;
const token = localStorage.FBIdToken;
if(token){
const decodedToken = jwtDecode(token);
if(decodedToken.exp * 1000 < Date.now()){
window.location.href='/login'
authenticated = false;
} else {
authenticated = true;
};
}
class App extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<Router>
<Navbar/>
<div className="container">
<Switch>
<Route exact path="/" component={home}/>
<AuthRoute exact path="/login" component={login} authenticated={authenticated}/>
<AuthRoute exact path="/signup" component={signup} authenticated={authenticated}/>
</Switch>
</div>
</Router>
</Provider>
</MuiThemeProvider>
);
}
}
export default App;
Login page:
import React, { Component } from 'react'
import withStyles from '#material-ui/core/styles/withStyles'
import PropTypes from 'prop-types'
import AppIcon from '../images/micrologo.png'
import { Link } from 'react-router-dom'
//MUI Stuff
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography'
import TextField from '#material-ui/core/TextField'
import Button from '#material-ui/core/Button'
import CircularProgress from '#material-ui/core/CircularProgress'
//Redux stuff
import { connect } from 'react-redux';
import { loginUser } from '../redux/actions/userActions';
const styles = (theme) => ({
...theme.spreadThis
})
class login extends Component {
constructor(){
super();
this.state = {
email: '',
password: '',
errors: {}
}
}
componentWillReceiveProps(nextProps){
if(nextProps.UI.errors){
this.setState({ errors: nextProps.UI.errors })
}
}
handleSubmit = (event) => {
event.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password
};
this.props.loginUser(userData, this.props.history)
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
const { classes, UI: { loading } } = this.props;
const { errors } = this.state;
return (
<Grid container className={classes.form}>
<Grid item sm/>
<Grid item sm>
<img src={AppIcon} alt="micrologo" className={classes.image}/>
<Typography variant="h3" className={classes.pageTitle}>
Login
</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField
id="email"
name="email"
type="email"
label="Email"
className={classes.textField}
helperText={errors.email}
error={errors.email ? true : false}
value={this.state.email}
onChange={this.handleChange}
fullWidth
/>
<TextField
id="password"
name="password"
type="password"
label="Password"
className={classes.textField}
helperText={errors.password}
error={errors.password ? true : false}
value={this.state.password}
onChange={this.handleChange}
fullWidth
/>
{errors.general && (
<Typography variant="body2" className={classes.customError}>
{errors.general}
</Typography>
)}
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
disabled={loading}
>
Login
{loading && (
<CircularProgress size={30} className={classes.progress}/>
)}
</Button>
<br />
<small>Don't have an account? Sign up <Link to="/signup">here</Link>
</small>
</form>
</Grid>
<Grid item sm/>
</Grid>
)
}
}
login.propTypes = {
classes: PropTypes.object.isRequired,
loginUser: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
UI: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
user: state.user,
UI: state.UI
});
const mapActionsToProps = {
loginUser
}
export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(login));
User Details:
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../type';
import axios from 'axios'
export const loginUser = (userData, history) => (dispatch) => {
dispatch({ type: LOADING_UI});
axios
.post('/login', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
dispatch(getUserData());
dispatch({ type: CLEAR_ERRORS });
history.push('/');
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
})
})
}
export const getUserData = () => (dispatch) => {
axios.get('/user')
.then((res) => {
dispatch({
type: SET_USER,
payload: res.data
})
})
.catch((err) =>
console.log(err)
)
}
EDIT: After making changes to my User Actions file that references my User Reducer, could it possibly be something to do with the User Reducer code?
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI, SET_AUTHENTICATED, SET_UNAUTHENTICATED } from '../type';
const initialState = {
authenticated: false,
credentials: {},
likes: [],
notifications: []
};
export default function(state = initialState, action){
switch(action.type){
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState
case SET_USER:
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
EDIT 2: Adding copy > copy fetch code from network tab:
fetch("http://localhost:3000/login", {
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9",
"if-none-match": "W/\"717-3FVndTj2FHm3TgZjXTrLARSY62Q\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-origin",
"upgrade-insecure-requests": "1"
},
"referrer": "http://localhost:3000/login",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
});
You can try the following:
export const loginUser = (userData, history) => (
dispatch
) => {
dispatch({ type: LOADING_UI });
axios
.post("/login", userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem("FBIdToken", FBIdToken);
axios.defaults.headers.common[
"Authorization"
] = FBIdToken;
//wait for getUserData to finish
return getUserData()(dispatch);
})
.then(() => {
//getUserData is finished
dispatch({ type: CLEAR_ERRORS });
history.push("/");
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data,
});
});
};
export const getUserData = () => (dispatch) => {
//return a promise so loginUser action can wait for
// it to finish
return axios.get("/user").then((res) => {
dispatch({
type: SET_USER,
payload: res.data,
});
});
//if you catch it then the catch that set errors is
// pointless
// .catch((err) => console.log(err));
};
Two components are rendered with conditional rendering with a redux state but after changing redux state from one component warning appears that can't perform state update on unmounted component. In LoginWrapper.js Login and Redirect are two components with conditional rendering using isLoggedIn state of redux.
LoginWrapper.js
import React from 'react';
import Login from 'containers/Login';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { PropTypes } from 'prop-types';
const LoginWrapper = ({ isLoggedIn }) => {
return (
<div>
{
!isLoggedIn
?
<Login />
:
<Redirect to="/profile" />
}
</div>
)
}
LoginWrapper.defaultProps = {
isLoggedIn: false
}
LoginWrapper.propTypes = {
isLoggedIn: PropTypes.bool
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.auth.isLoggedIn
}
}
export default connect(mapStateToProps)(LoginWrapper);
Login.js
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter, Link } from 'react-router-dom';
import { withFormik, Form, Field } from 'formik';
import { connect } from 'react-redux';
import { logInUser } from 'actions/auth';
import { logInUrl } from "apis";
import ModalLayout from "shared/ModalLayout";
import * as Yup from 'yup';
const loginPost = (history, values, setSubmitting, setErrors, resetForm, logIn) => {
const { username, password } = values;
window.fetch(logInUrl, {
method: 'POST',
credentials: "same-origin",
headers: {
'Content-Type': "application/json"
},
body: JSON.stringify({
"username": username,
"password": password
})
})
.then((results) => {
return results.json();
})
.then((data) => {
if(data.errors) {
setErrors({ 'username': data.errors[0].msg });
} else {
logIn(data.user, history);
resetForm();
}
setSubmitting(false);
})
.catch((err) => {
console.log(err);
})
}
const LogInForm = ({
touched,
errors,
isSubmitting,
}) => (
<ModalLayout>
<Form className="login-form">
{touched.username && errors.username && <p className="login-error">{errors.username}</p>}
<div className="login-username">
<Field type="input" placeholder="Username" name="username" />
</div>
{touched.password && errors.password && <p className="login-error">{errors.password}</p>}
<div className="login-password">
<Field type="password" placeholder="Password" name="password" />
</div>
<div className="login-button">
<button className="modal-button login-button" type="submit" disabled={isSubmitting}>
Log in
</button>
</div>
<div className="login-redirect">
<Link to="/signup">Don't have an account.Create one</Link>
</div>
</Form>
</ModalLayout>
);
LogInForm.propTypes = {
isSubmitting: PropTypes.bool.isRequired,
errors: PropTypes.object.isRequired,
touched: PropTypes.object.isRequired,
}
const FormikApp = withFormik({
mapPropsToValues() {
return {
username: '',
password: '',
}
},
handleSubmit(values, { resetForm, setErrors, setSubmitting, props }) {
const { logIn, history } = props;
loginPost(history, values, setSubmitting, setErrors, resetForm, logIn);
},
validationSchema: Yup.object().shape({
username: Yup.string().required('Username is required'),
password: Yup.string().required('Password is required'),
})
})(LogInForm);
export default withRouter(connect(null, { logIn: logInUser })(FormikApp));
actions
import {
LOG_IN,
LOG_OUT,
} from 'actions/types';
import { logInUrl } from 'apis';
export const logInUser = (user) => {
return (dispatch) => {
dispatch({
type: LOG_IN,
payload: user
})
}
}
export const logOutUser = () => {
return {
type: LOG_OUT
}
}
reducers
import { LOG_IN, LOG_OUT } from 'actions/types';
const INITIAL_STATE = {
isloggedIn: null,
user: null,
uid: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case LOG_IN:
return { ...state, isLoggedIn: true, user: action.payload, uid: action.payload.id }
case LOG_OUT:
return { ...state, isLoggedIn: false, user: null, uid: null };
default:
return state;
}
};