React redux slice - Accessing to state changes after logged in - reactjs

I'm trying to use a combination of a slice react redux to store my token credentials with a dispatch action to update the state in a call to my API.
Finally in the login component i was trying to verify if somehow that token state changed to verify if user can access.
But this condition in my component loginHandler function:
if(auth){ history.push('/wallet') }
Seems to be always empty.
This is my redux slice:
import {createSlice} from "#reduxjs/toolkit";
const authSlice = createSlice({
name: 'auth',
initialState: {
token: {
access: '',
refresh: ''
}
},
reducers: {
login(state, action){
state.token = action.payload
},
logout(state){
state.token.access = ''
state.token.refresh = ''
}
}
})
export const authActions = authSlice.actions
export default authSlice
This is my action:
import http from "../http-common";
import {authActions} from "./auth-slice";
export const login = (data) => {
return async (dispatch) => {
const loginRequest = async () => {
const response = await http.post('/api/token/login', data);
return response.data;
};
try {
const sessionsData = await loginRequest();
console.log(sessionsData)
localStorage.setItem("token", JSON.stringify(sessionsData))
console.log(sessionsData)
dispatch(
authActions.login(sessionsData)
)
} catch (error) {
console.log(error)
}
};
}
And inside my component i'm using the dispatch to my action and the useSelector to track changes to my store state:
import React, {useState, useContext} from 'react';
import {useHistory} from "react-router-dom";
import LoginForm from '../../components/forms/LoginFormPage'
import {useSelector, useDispatch} from "react-redux";
import {login} from "../../store/auth-actions";
const Login = () => {
let history = useHistory();
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false)
const auth = useSelector((state) => state.auth.token.access);
const handleLogin = (data) => {
console.log(auth)
setIsLoading(true)
dispatch(login({email: data.email, password: data.password})).then()
console.log(auth)
if(auth){
history.push('/wallet')
}
setIsLoading(false)
}
return (
<LoginForm
onLoginSubmit={handleLogin}
isLoading={isLoading}/>
)
}
export default Login;

Related

How can I await for an async action dispatch on the click of a button?

I have this react component where in the user wants to send in a login request but whenever I dispatch the action, even before it is executed the further code in my component is executed.
I've tried making the login request function as async and even tried using await before dispatching the action but it's all been in vain.
Component file:
import React from 'react';
import BaseButton from '../BaseButton/BaseButton';
import { useState } from 'react';
import { userLogin } from '../../redux/auth/authActions';
import axios from 'axios';
import {connect} from 'react-redux'
function Login({ isLoggedIn, userLogin }) {
const [login, setLogin] = useState(true); //to see if the user wats to login or sign up
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const handleLogin = () => {
let userCredentials = {
email: email,
password: password
}
userLogin(userCredentials); // <------ i want to wait for this to execute before the below code is executed
if (isLoggedIn) {
console.log('im here');
} else {
console.log('wrong credentials');
}
}
const handleSignUp = async () => {
}
return login ? (
<>
{*/ ...some JSX for user input */}
<div className="flex justify-center">
<BaseButton variant={'solid'} onClick = {handleLogin}>Submit</BaseButton>
</div>
{*/ ...some more JSX for user input */}
<>
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.auth.isLoggedIn
}
}
const dispatchStateToProps = (dispatch) => {
return {
userLogin: (userCredentials) => dispatch(userLogin(userCredentials))
}
}
export default connect(mapStateToProps, dispatchStateToProps)(Login);
authActions:
import {
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGIN_FAILURE,
} from './authTypes';
import axios from 'axios';
export const sendLoginRequest = () => {
return {
type: USER_LOGIN_REQUEST,
};
};
export const loginSucccess = () => {
return {
type: USER_LOGIN_SUCCESS,
};
};
export const loginFailure = (error) => {
return {
type: USER_LOGIN_FAILURE,
payload: error,
};
};
export const userLogin = (userCredentials) => {
return (dispatch) => {
try {
dispatch(sendLoginRequest());
axios
.post('http://localhost:3001/auth/login', userCredentials)
.then((data) => {
console.log(data.status);
dispatch(loginSucccess());
})
.catch(err => {
console.log("incorrect credentials");
dispatch(loginFailure('incorrect credentials'));
});
} catch(err) {
dispatch(loginFailure(err.message));
}
};
};
auth reducer file:
import {
USER_LOGIN_REQUEST,
USER_LOGIN_FAILURE,
USER_LOGIN_SUCCESS,
} from './authTypes';
const initialState = {
loading: false,
isLoggedIn: false,
error: ''
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return {
...state,
loading: true
}
case USER_LOGIN_SUCCESS: return{
...state,
loading: false,
isLoggedIn: true,
}
case USER_LOGIN_FAILURE: return{
...state,
loading: false,
isLoggedIn: false,
error: action.payload
}
default: return state;
}
};
export default authReducer;
my store file:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from '../rootReducer';
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
root reducer:
import {combineReducers} from 'redux';
import authReducer from './auth/authReducer';
const rootReducer = combineReducers({
auth: authReducer
});
export default rootReducer;
The userLogin action isn't declared async nor does it return a Promise so this is why your handleLogin handler isn't able to wait for it to complete.
Convert userLogin to an async action function.
export const userLogin = (userCredentials) => async (dispatch) => {
try {
dispatch(sendLoginRequest());
const data = await axios.post('http://localhost:3001/auth/login', userCredentials);
console.log(data.status);
dispatch(loginSucccess());
return true; // <-- return resolved value
} catch(err) {
dispatch(loginFailure(err.message));
return false; // <-- return resolved value
}
};
Convert handleLogin to an async function so it can await the dispatched action to resolve. Note that handleLogin won't, and can't see any updated isLoggedIn state value from Redux while it has a current value closed over in scope from the time it was called.
const handleLogin = async () => {
const userCredentials = { email, password };
const authSuccess = await userLogin(userCredentials);
if (authSuccess) {
console.log('I'm here');
} else {
console.log('wrong credentials');
}
};
use async await or then catch in handleLogin function and also do not forgot to add return in userLogin and sub functions
`const handleLogin = async () => {
await userLogin(userCredentials);
if(isLoggedIn) {
console.log('here');
}
}`
or use then
`userLogin(userCredentials).then(() => { if(isLoggedIn){
console.log('here');
}});`

React redux state does not mutate state fast enough on login to ensure that user type specific routing enables the correct Routes

I am building a React redux app which on login updates the redux initialState. Based on the user type stored in redux certain routes are enabled. Unfortunately the state mutation of redux seems to be not fast enough, since on redirect from the login the route is not available. I tried various timeouts, which - however - did not work.
Here you have an excerpt from my authSlice in redux
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import {} from '../services/auth.service';
const initialState = {
loggedIn: false,
userData: {}
};
export const loginAsync = createAsyncThunk(
'auth/login',
async (credentials) => {
return await login(credentials);
}
);
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
},
extraReducers: (builder) => {
builder
.addCase(loginAsync.fulfilled, (state, action) => {
state.userData = action.payload.userData;
state.loggedIn = true;
})
},
});
export const selectIsLoggedIn = (state) => state.auth.loggedIn;
export const selectUserData = (state) => state.auth.userData;
export const selectUserType = (state) => state.auth.userData.userType;
export default authSlice.reducer;
The login component looks the following
import React, {useEffect, useState} from "react";
import {Link, Redirect} from 'react-router-dom';
import {selectIsLoggedIn, selectUserType, loginAsync, selectUserData, setUserData} from "../../../slice/authSlice";
import { useDispatch, useSelector } from "react-redux";
import { history } from "../../../helpers/history";
import '../../../styles/main/Login.css';
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
//Change handlers
const changeEmail = (e) => {
setEmail(e.target.value);
};
const changePassword = (e) => {
setPassword(e.target.value);
};
/**
* #function (01) prevent the default form behaviour and set to loading
* (02) dispatch the login
* (03) redirect based on user type
*/
const performLogin = (e) => {
//01
e.preventDefault();
setLoading(true);
//02
dispatch(loginAsync({email: email, password: password}))
.then((res) => {
//03
res.payload.userData.userType === 'student' ? history.push("/student/search/") : res.payload.userType === 'coder' ? history.push("/coder/editor/") : history.push("/admin/invite/");
window.location.reload();
})
.catch(() => {
setLoading(false);
});
};
return (
<div className="login_wrapper">
login npx stuff...
</div>
);
};
export default Login;
The Routes are based on a use selector redux operation where I obtain the loggedin state and user type. This routes component is directly included in the App component.
If I set the initialState to the respective states (loggedin true and usertype = student) everything works fine.
Any help is highly appreciated

React.js Authentication Problem Even I Get the Token

I'm building a site with basic auth with Spring. I use Redux. I'm sending a request to "/auth" in the backend. After successfully logging in, i get those:
enter image description here
As you can see I have successfully logged in.
But i still can not be authenticated. I did not refresh the page i did nothing but this is the console output. By the way, postman is working fine.
enter image description here
This is part of apiCalls:
import axios from "axios";
export const signup = (body) => {
return axios.post('/users', body);
};
export const login = creds => {
return axios.post('/auth', {}, {auth:creds});
};
export const getMarketItemsSortByDate = () => {
return axios.get("/market/last");
}
This is configureStore:
import {createStore, applyMiddleware,compose} from 'redux';
import authReducer from './authReducer';
import SecureLS from 'secure-ls';
import thunk from 'redux-thunk';
const secureLS = new SecureLS();
const getStateFromStorage = () => {
const hoaxAuth = secureLS.get('hoax-auth');
let stateInLocalStorage = {
isLoggedIn:false,
username:undefined,
mail:undefined,
balance:undefined,
password:undefined
};
if(hoaxAuth){
stateInLocalStorage = hoaxAuth;
}
return stateInLocalStorage;
}
const updateStateInStorage = newState => {
secureLS.set('hoax-auth', newState);
}
const configureStore = () => {
const initialState = getStateFromStorage();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(authReducer, initialState, composeEnhancers(applyMiddleware(thunk)));
store.subscribe(()=> {
updateStateInStorage(store.getState());
})
return store;
}
export default configureStore;
This is authActions :
import * as ACTIONS from "./Constants";
import {login} from '../api/apiCalls';
export const logoutSuccess = () => {
return {
type: ACTIONS.LOGOUT_SUCCESS
};
}
export const loginSuccess = authState => {
return {
type: ACTIONS.LOGIN_SUCCESS,
payload: authState
};
};
export const loginHandler = credentials => {
return async function(dispatch) {
const response = await login(credentials);
const authState = {
...response.data,
password: credentials.password,
};
console.log(authState);
dispatch(loginSuccess(authState));
return response;
};
};
And this is authReducer:
import * as ACTIONS from './Constants';
const defaultState = {
isLoggedIn:false,
username:undefined,
mail:undefined,
balance:undefined,
password:undefined
}
const authReducer = (state= { ...defaultState},action) => {
if(action.type === ACTIONS.LOGOUT_SUCCESS){
return defaultState;
} else if(action.type === ACTIONS.LOGIN_SUCCESS){
return {
...action.payload,
isLoggedIn:true
}
}
return state;
}
export default authReducer;
Everything is fine with postman so problem should be inside React.js
Make sure to set withCredentials for each requests you made with axios that requires the token as such:
export const login = creds => {
return axios.post('/auth', {}, {auth:creds, withCredentials:true});
};
export const getMarketItemsSortByDate = () => {
return axios.get("/market/last", {}, {withCredentials:true});
}
which allows the access token to be set and to be sent along with the request.

Clear / delete all states when login out react app

When a user log to a react app, I fill data to authState object. Inside the app I fill other state objects with data. I want to clear all those states when the user logout
for example I have this provider
import { createContext, useEffect, useReducer } from "react";
import auth from "./reducers/auth";
import pendiente from "./reducers/pendiente";
import historico from "./reducers/historico";
import authInitialState from "./initialStates/authInitialState";
import pendienteInitialState from "./initialStates/pendienteInitialState";
import historicoInitialState from "./initialStates/historicoInitialState";
export const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [authState, authDispatch] = useReducer(auth, [], () => {
const localData = localStorage.auth;
return localData ? JSON.parse(localData): authInitialState;
});
const [pendienteState, pendienteDispatch] = useReducer(
pendiente,
pendienteInitialState
);
const [historicoState, historicoDispatch] = useReducer(
historico,
historicoInitialState
);
useEffect(() => {
localStorage.auth = JSON.stringify(authState);
}, [authState]);
return (
<GlobalContext.Provider
value={{
authState,
authDispatch,
pendienteState,
pendienteDispatch,
historicoState,
historicoDispatch,
}}
>
{children}
</GlobalContext.Provider>
);
};
In Logout function I'm sending and action (logout) with 3 dispatchs.
const {
authState,
authDispatch,
pendienteDispatch,
historicoDispatch,
} = useContext(GlobalContext);
const handleLogout = () => {
logout(history)(authDispatch, pendienteDispatch, historicoDispatch);
};
Inside the action I send a dispatch an to every sate objcet to clear the data with it's initial state
This works fine, but I think this is not the correct way to do it
const logout = (history) => (
dispatch,
pendienteDispatch,
historicoDispatch
) => {
localStorage.removeItem("token");
dispatch({ type: LOGOUT_USER });
pendienteDispatch({ type: CLEAR_PENDIENTE_DATA });
historicoDispatch({ type: CLEAR_HISTORICO_DATA });
history.push("/");
};
¿Any ideas ?

Why doesn't useEffect hook work on page refresh?

I'm working on a react project. I have my own API to fetch information. I'm using the useEffect hook to fetch profile information from API. My problem is when page mounts for the first time i can fetch the data with no problem but if i refresh the page it doesn't work at all. I know i have to give a second parameter to useEffect. I tried to put profile as the second argument even dispatched the getCurrentProfile function but when i do that it constantly fires off fetch request. I would be glad if anyone can help me with that. Thanks.
Here is my Profile component:
export const Profile = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getCurrentProfile());
}, [])
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const { user } = authReducer;
console.log("loading", loading)
console.log("profile", profile)
return loading && profile === null ? (
<div >
<Spinner />
</div>
) :
Here is my Profile action:
export const getCurrentProfile = () => async dispatch => {
try {
const res = await axios.get("/api/profile/me");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
})
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
})
}
}
Here is my profile reducer:
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
}
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
}
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
}
default:
return state;
}
}
You might want to try adding conditional logic within the useEffect so you only trigger the dispatch if you don't already have a profile.
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useCallback } from "react";
import { getCurrentProfile } from "./action";
export const Profile = () => {
const dispatch = useDispatch();
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
// read more about this here: https://stackoverflow.com/questions/58624200/react-hook-useeffect-has-a-missing-dependency-dispatch
const stableDispatch = useCallback(dispatch, []);
useEffect(() => {
if (!profile) {
stableDispatch(getCurrentProfile());
}
}, [profile, stableDispatch]);
const { user } = authReducer;
console.log("loading", loading);
console.log("profile", profile);
return loading && profile === null ? <div>Spinner</div> : "Actual Profile";
};
export default Profile;
Also, it doesn't seem like you're currently doing anything with the loading piece of state–at least from what you've shared here. You might want to dispatch an action indicating that you're loading before you start the fetch and then it will be set to false when you get the response.
Check out this codesandbox for reference: https://codesandbox.io/s/focused-kilby-gd2nr?file=/src/App.js
Reducers:
const initialState = {
profile: null,
loading: false
};
export const profile = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case "LOADING_PROFILE":
return {
...state,
loading: true
};
case "GET_PROFILE":
return {
...state,
profile: payload,
loading: false
};
case "PROFILE_ERROR":
return {
...state,
error: payload,
profile: null
};
case "CLEAR_PROFILE":
return {
...state,
profile: null,
loading: false
};
default:
return state;
}
};
export const auth = (state = {}, action) => {
return state;
};
Action Creator:
import axios from "axios";
export const getCurrentProfile = () => async (dispatch) => {
try {
dispatch({ type: "LOADING_PROFILE" });
const res = await axios.get("https://jsonplaceholder.typicode.com/users/1");
console.log(res);
dispatch({
type: "GET_PROFILE",
payload: res.data.data
});
} catch (err) {
dispatch({
type: "PROFILE_ERROR",
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { profile, auth } from "./reducers";
import App from "./App";
import thunk from "redux-thunk";
const store = createStore(
combineReducers({
profile,
auth
}),
applyMiddleware(thunk)
);
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Well i solved it by dispatching 'getCurrentProfile' not 'getCurrentProfile()' turns out using it like a function causes continuously firing off.
const profileReducer = useSelector((state) => state.profile);
const authReducer = useSelector((state) => state.auth);
const { profile, error, loading } = profileReducer;
const dispatch = useDispatch();
useEffect(() => {
if (!profile) {
console.log("It worked")
dispatch(getCurrentProfile());
}
}, [dispatch(getCurrentProfile)])

Resources