React reducers, state not updating immediately - reactjs

i am working on createContext and reducers, with reducers i am updating state value, but it is not updating value at a time, i did console.log(user); in onSubmitSave(), but in console log i am not getting its updated state at a time, i can see it works when i clicked on signin again, here i have attached my whole code, can anyone please look into it, and help me to resolve this issue ?
auth.js
import { createContext } from 'react';
const AuthContext = createContext({
user: null,
hasLoginError: false,
login: () => null,
logout: () => null
});
export default AuthContext;
signin.js
// Login button Click
async function onSubmitSave(e) {
e.preventDefault();
const messages = {
'itemUsername.required': 'Username cannot be empty.',
'itemPassword.required': 'Password cannot be empty.',
};
const rules = {
itemUsername: 'required',
itemPassword: 'required|string',
};
validateAll(state, rules, messages)
.then(async () => {
let data = await login(itemUsername, itemPassword);
console.log(data);
console.log("sdsds");
console.log(user);
setUserData(user);
})
.catch(errors => {
const formattedErrors = {
};
errors.forEach(error => formattedErrors[error.field] = error.message);
setState({
...state,
errors: formattedErrors
});
})
}
// End Login
App.js
const reducer = (state, action) => {
switch (action.type) {
case "login": {
const { username, password } = action.payload;
return {
...state,
hasLoginError: false,
user: {
id: 1,
username: USERNAME,
firstName: "Dev",
lastName: "To"
}
};
}
case "logout":
return {
...state,
user: null
};
default:
throw new Error(`Invalid action type: ${action.type}`);
}
};

Related

React useEffect not triggered

My useEffect code looks like this:
useEffect(() => {
if (user) {
setBalance(user.user_balance);
console.log("user.user_balance");
console.log(user.user_balance);
}
}, [user]);
Full code : https://pastebin.com/CP6sLNQZ
The useEffect is supposed to get triggered every time user (user_balance) gets updated in the database but it is not getting triggered. User is a props and it is passed from redux to the component.
The action for updating the balance is here:
import axios from "axios";
import { returnErrors } from "./errorActions";
import { UPDATE_BALANCE_SUCCESS } from "./types";
//update balance
export const updateBalance = ({ email, user_balance }) => (dispatch) => {
// Headers
const config = {
headers: {
"Content-Type": "application/json",
},
};
// Request body
const body = JSON.stringify({ email, user_balance });
axios
.post("/api/users/updatebalance", body, config)
.then((res) =>
dispatch({
type: UPDATE_BALANCE_SUCCESS,
payload: { email, user_balance },
})
)
.catch((err) =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
The reducer:
import { UPDATE_BALANCE_SUCCESS } from "../actions/types";
const initalState = {
email: null,
user_balance: null,
};
export default function (state = initialState, action) {
switch (action.type) {
case UPDATE_BALANCE_SUCCESS:
return {
...state,
email: action.payload.email,
user_balance: action.payload.user_balance,
};
}
}
And the end-point:
// #route POST api/users/updatebalance
// #description Update user balance
// #access Public
router.post("/updatebalance", (req, res) => {
const { email, user_balance } = req.body;
User.findOneAndUpdate(
{ email },
{ $inc: { user_balance: user_balance } },
function (err, result) {
if (err) {
res.send(err);
} else {
res.send(result);
}
}
);
});
Also where auth comes from:
This is the auth reducer: https://pastebin.com/upHdvtiF
These are the actions: https://pastebin.com/Ajp7S1Yi
And this is the end point: https://pastebin.com/NwCHCitY
I got a log in and register page and once a user gets registered auth gets created. In the actions link you can see the login and register action.
I think you have to add the user layer
const initalState = {
user: {
email: null,
user_balance: null,
}
};
and the reducer
export default function (state = initialState, action) {
switch (action.type) {
case UPDATE_BALANCE_SUCCESS:
return {
...state,
user: {
...state.user,
email: action.payload.email,
user_balance: action.payload.user_balance,
}
};
}
}

React.js: How to get and show current user when logged in?

In my React app I am working in user login. My goal is to show current user's username when the user is logged in. I'm fetching the user data in redux actions and, as I followed some tutorials, I need to get jwt token coming from backend in fetch function. In login Fetch function I'm trying to get and save the token(see fetching function), but it shows undefined in devtools/localStorage. This is how InitialState updates in LoginSuccess in Reducers.
state
{user: {…}, loading: true, error: "", isAuthenticated: false, users: {…}}
error: ""
isAuthenticated: false
loading: true
user: {user: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRVc…xMTB9.hNsYTKGYIFRsPXw66AhB1o0EXyyfgfRTzOFzqBfjaTg"}
users: {user: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRVc…xMTB9.hNsYTKGYIFRsPXw66AhB1o0EXyyfgfRTzOFzqBfjaTg"}
__proto__: Object
I don't know how to get access to the current logged in user data: username or firstName for instanse.
Any help will be appreciated.
Actions
import axios from 'axios'
import { Dispatch } from 'redux'
import {
FETCH_USER_REQUEST,
UserActions,
User,
LOGIN_USER_SUCCESS,
FETCH_LOGIN_FAILURE,
LOGOUT,
} from '../../types/UserType'
export const fetchUserRequest = () => {
return {
type: FETCH_USER_REQUEST,
}
}
export const fetchLoginFailure = (error: UserActions) => {
return {
type: FETCH_LOGIN_FAILURE,
payload: error,
}
}
export function logout(): UserActions {
return {
type: LOGOUT,
}
}
export function loginSuccess(user: User): UserActions {
return {
type: LOGIN_USER_SUCCESS,
payload: {
user,
},
}
}
export const login = ({ email, password }: any) => {
return (dispatch: Dispatch) => {
dispatch(fetchUserRequest())
axios
.post('http://localhost:8000/logIn', {
email: email,
password: password,
})
.then((response) => {
const users = response.data
dispatch(loginSuccess(users))
localStorage.setItem('jwt', users.auth_token)
console.log('users', users) // undefined
})
.catch((error) => {
dispatch(fetchLoginFailure(error.message))
})
}
}
Reducer
import {
LOGIN_USER_SUCCESS,
UserActions,
UserState,
LOGOUT,
} from '../../types/UserType'
const initialState: UserState = {
user: {},
loading: false,
error: '',
isAuthenticated: false,
}
const UserReducer = (state = initialState, action: UserActions) => {
switch (action.type) {
case LOGIN_USER_SUCCESS:
console.log('state', state) // initialState update see above
return {
...state,
loading: false,
user: action.payload,
users: action.payload,
isAuthenticated: true,
error: '',
}
case LOGOUT:
return {
...state,
isAuthenticated: false,
user: null,
users: [],
}
default:
return state
}
}
export default UserReducer
And I assume I am going to show user userName or firstName in logout component
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { Icon, Button } from 'semantic-ui-react'
import { logout } from '../../../redux/User/UserActions'
import { AppState } from '../../../types'
function Logout() {
const dispatch = useDispatch()
const user = useSelector((state: AppState) => state.user.user)
console.log('user', user)
const logoutOnClick = () => {
dispatch(logout())
localStorage.clear()
}
return (
<Button
color="black"
as={Link}
to="Login"
name="logout"
onClick={logoutOnClick}
>
<Icon name="sign out"> </Icon>Logout
</Button>
)
}
export default Logout
You save your logged-in data to localStorage like auth_token you did and clear in logout function.
axios
.post('http://localhost:8000/logIn', {
email: email,
password: password,
})
.then((response) => {
const users = response.data
dispatch(loginSuccess(users))
localStorage.setItem('jwt', users.auth_token)
localStorage.setItem('user', JSON.stringify(users))
console.log('users', users) // undefined
})
.catch((error) => {
dispatch(fetchLoginFailure(error.message))
})
and access inside your logout component or wherever you need that
let userDetails = JSON.parse(localStorage.getItem('user'));
and clear it inside logout function
const logoutOnClick = () => {
dispatch(logout())
localStorage.clear() // already clearing
}

Redux Thunk - Get Updated Store in Promise

I'm having an issue trying to use Redux Thunk for implementing JWT Authentication in my app: when I perform login and retrieve the promise from the action, I do not get the updated store (I still have the previous value).
Here is my code:
const Login = ({ doLogin, token }) => {
const submitForm = (e) => {
doLogin(email, password).then(function () {
console.log(token);
});
}
};
return (
// Some JSX that calls submitForm()
);
};
Login.propTypes = {
token: PropTypes.string.isRequired,
doLogin: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
console.log(state);
return {
token: state.auth.access_token,
};
}
const mapDispatchToProps = { doLogin };
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Here is my action creator:
export function doLogin(email, password) {
return (dispatch) => {
dispatch(start());
return axios
.post(config.api.url + "/login", {
email,
password,
})
.then((success_rsp) => {
if (success_rsp.data.success) {
dispatch(success(success_rsp.data.access_token));
} else {
dispatch(error());
}
})
.catch((error_rsp) => dispatch(error()));
};
function start() {
return { type: types.AUTH_LOGIN };
}
function success(access_token) {
return {
type: types.AUTH_LOGIN_SUCCESS,
is_authenticated: true,
access_token: access_token
};
}
function error() {
return {
type: types.AUTH_LOGIN_ERROR,
};
}
}
And here is my reducer:
const authReducer = (state = initialState.auth, action) => {
switch (action.type) {
case types.AUTH_LOGIN:
return {
...state,
is_loading: true,
};
case types.AUTH_LOGIN_SUCCESS:
return {
...state,
is_loading: false,
is_authenticated: action.is_authenticated,
access_token: action.access_token
};
case types.AUTH_LOGIN_ERROR:
return {
...state,
is_loading: false,
is_authenticated: false,
access_token: "",
};
default:
return state;
}
};
The problem is that when I get in the Promise of my Login component, it logs the previous value of the token, and not the one received by the API.
However, when I log the state in the mapStateToProps function, I see that the function is called 3 times in total (one when arriving on the page, one when starting the doLogin, and one on the success), and it logs the right value for the token.
Do you know why the token I get is not updated? And how I could get an updated one?
Thank you!

Handling Local state and Redux State within a component

When a user tries to login, the Local state or Redux state should be updated. this.props.checkLogin() refers to the Redux store.
If the login is succesful, the Redux store should be updated.
Because this information is needed throughout the whole application.
If not succesful, the localstate should be updated with an error
message (a boolean which is called 'showError').
In the setup below, the results are always updated in the Redux store (which is not what I want, but I dont see any other solution yet). The main problem is that I can not catch an error in the checkLogin() of the component, if there is an error in the action.
// LOGIN COMPONENT (Code 1)
checkLogin(e){
e.preventDefault()
var username = e.target.elements.username.value
var password = e.target.elements.password.value
this.props.checkLogin(username, password)
}
// Redux action
export const checkLogin = (username, password) => async dispatch => {
axios.post('/api/login', {
username: username,
password: password
})
.then(res => {
const token = res.data.token;
localStorage.setItem('jwtToken', token);
setAuthorizationToken(token);
dispatch(setCurrentUser(jwt.decode(token)))
})
.catch(err => {
dispatch({
type: AUTH_ERROR,
payload: true
})
})
}
However, if there is an error in (Code 1), the Redux state is updated (with the AUTH_ERR action). I want to get rid of this action, as I only want it in local state. Because the boolean of an error login attempt should not be stored in Redux.
The only solution which I could think of is shown below (Code 2).
The POST-request has moved to the component itself (which is not nice in my opinion).
// LOGIN COMPONENT (Code 2)
checkLogin(e){
e.preventDefault()
var username = e.target.elements.username.value
var password = e.target.elements.password.value
axios.post('/api/login', {
username: username,
password: password
})
.then(res => {
this.props.checkLogin(username, password)
}
.catch(err => {
this.setState({
showError: true
})
})
}
// Redux action
export const checkLogin = (username, password) => async dispatch => {
const token = res.data.token;
localStorage.setItem('jwtToken', token);
setAuthorizationToken(token);
dispatch(setCurrentUser(jwt.decode(token)))
}
My main question is: what is the cleanest way of solving this? Is there some sort of guide which I can follow? The code (Code 2) works, but it definitely lacks design principles.
Another way of solving this could be something like this: use the first code and throw an error in the catch of the POST-request. Which is catched by the component, like this:
// LOGIN COMPONENT (Code 3)
checkLogin(e){
e.preventDefault()
var username = e.target.elements.username.value
var password = e.target.elements.password.value
this.props.checkLogin(username, password).catch(res => {
this.setState({
showError: true
})
})
}
// Redux action
export const checkLogin = (username, password) => async dispatch => {
axios.post('/api/login', {
username: username,
password: password
})
.then(res => {
const token = res.data.token;
localStorage.setItem('jwtToken', token);
setAuthorizationToken(token);
dispatch(setCurrentUser(jwt.decode(token)))
})
.catch(err => {
// Return an error here which is cacthed in the component
})
}
In the above code (Code 3), I can't see how to solve this. Is it possible to throw an error in the Redux action, which is then catched by the checkLogin() of the Login-component? Or should I complete take another path in solving this?
You have connected redux to manage the state of the application, why do you want to avoid storing an error in redux? A typical implementation of the react-redux (with redux-thunk middleware) authentication process is as follows.
actions.js
export const auth = (username, password) => {
dispatch(authStart())
axios.post('/api/login', {
username: username,
paswword: password
}).then(request => {
dispatch(authSuccess(response.authToken, response.userId))
}).catch(error => {
dispatch(authFail(error))
})
})
const authStart = () => {
return { type: 'AUTH_START'}
}
const authSuccess = (authToken, userId) => {
return { type: 'AUTH_SUCCESS', authToken: authToken, userId: userId }
}
const authFail = (error) => {
return { type: 'AUTH_FAIL', error: error }
}
reducer.js
const initialState = {
userId: null,
authToken: null,
error: null
loading: false
}
const authStart = (state, action) => {
return { ...state, loading: true, error: null }
}
const authSuccess = (state, action) => {
return {
...state,
loading: false,
authToken: action.authToken,
userId: actions.userId
}
}
const authStart = (state, action) => {
return { ...state, loading: false, error: error }
}
const authReducer = (state = initialState, action) => {
switch action.type:
case 'AUTH_START':
return authStart(state, action)
case 'AUTH_SUCCESS':
return authSuccess(state, action)
case 'AUTH_FAIL':
return authFail(state, action)
default:
return state
}
component.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { auth } from './actions.js'
class MyComponent extends Component {
checkLogin(e){
e.preventDefault()
var username = e.target.elements.username.value
var password = e.target.elements.password.value
this.props.signIn(username, password)
}
render() {
let content = <div>Login succeed. User id is {this.props.userId}</div>
if (this.props.loading) {
content = <div>Loading...</div>
}
if (this.props.error) {
content = <div>Error: {this.props.error}</div>
}
return <React.Fragment>{content}</React.Fragment>
}
}
const mapStateToProps = state => {
return {
error: state.auth.error,
loading: state.auth.loading,
userId: state.auth.userId
}
}
const mapDispatchToProps = dispatch => {
return {
signIn: (username, password) => dispatch(auth(username, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

React Redux Axios: POST Request not receiving credentials from redux state

I have been working on authentication with my project. I have a REST api backend that serves JWT tokens. My front end stack is ReactJS, Redux, Axios and Redux Thunk.
My question is why when I submit my form it does not send any credentials?
But when I console log the action and payload on credChange it seems to be correct. Am I not setting the state somewhere?
Also, axios does not catch the 400 Bad Request error.
Here is my code:
AuthActions.js
export const credChange = ({ prop, value }) => {
return {
type: CRED_CHANGE,
payload: { prop, value },
};
};
export const logoutUser = () => {
return (dispatch) => {
dispatch({ type: LOGOUT_USER });
};
};
const loginSuccess = (dispatch, response) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: response.data.token,
});
};
const loginError = (dispatch, error) => {
dispatch({
type: LOGIN_USER_ERROR,
payload: error.response.data,
});
};
export const loginUser = ({ empNum, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
axios({
method: 'post',
url: 'http://127.0.0.1:8000/profiles_api/jwt/authTK/',
data: {
emp_number: empNum,
password,
},
})
.then(response => loginSuccess(dispatch, response))
.catch(error => loginError(dispatch, error));
};
};
AuthReducer.js
const INITIAL_STATE = {
empNum: '',
password: '',
empNumErr: null,
passwordErr: null,
authTK: null,
loading: false,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CRED_CHANGE:
return { ...state, [action.payload.prop]: action.payload.value };
case LOGIN_USER:
return {
...state,
...INITIAL_STATE,
loading: true,
};
case LOGOUT_USER:
return {
...state,
INITIAL_STATE,
};
case LOGIN_USER_SUCCESS:
return {
...state,
...INITIAL_STATE,
authTK: action.payload,
};
case LOGIN_USER_ERROR:
return {
...state,
...INITIAL_STATE,
empNumErr: action.payload.emp_number,
passwordErr: action.payload.password,
};
default:
return state;
}
};
LoginForm.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
credChange,
loginUser,
logoutUser,
} from '../Actions';
class LoginForm extends Component {
constructor() {
super();
this.onFormSubmit = this.onFormSubmit.bind(this);
this.renderEmpNumErr = this.renderEmpNumErr.bind(this);
this.empNumChange = this.empNumChange.bind(this);
this.passwordChange = this.passwordChange.bind(this);
}
onFormSubmit() {
const { empNum, password } = this.props;
this.props.loginUser({ empNum, password });
}
empNumChange(text) {
this.props.credChange({ prop: 'empNum', value: text.target.value });
}
passwordChange(text) {
this.props.credChange({ prop: 'password', value: text.target.value });
}
renderEmpNumErr() {
if (this.props.empNumErr) {
return (
<p>
{this.props.empNumErr}
</p>
);
}
return null;
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<label htmlFor="numberLabel">Employee Number</label>
<input
id="numberLabel"
type="password"
value={this.props.empNum}
onChange={this.empNumChange}
/>
<label htmlFor="passLabel">Password</label>
<input
id="passLabel"
type="password"
value={this.props.password}
onChange={this.passwordChange}
/>
<button type="submit">Login</button>
</form>
{this.renderEmpNumErr()}
</div>
);
}
}
const mapStateToProps = ({ counter }) => {
const {
empNum,
password,
loading,
empNumErr,
passwordErr,
authTK,
} = counter;
return {
empNum,
password,
loading,
empNumErr,
passwordErr,
authTK,
};
};
export default connect(mapStateToProps, { credChange, loginUser, logoutUser })(LoginForm);
After Submitting form with credentials
The console says:
POST XHR http://127.0.0.1:8000/profiles_api/jwt/authTK/ [HTTP/1.0 400 Bad Request 5ms]
And the POST request Raw Data is blank, therefore no credentials were sent.
{"emp_number":["This field is required."],"password":["This field is required."]}
EDIT
If there is any other information I can provide please say so but I think this should be sufficient.
Looks like empNum and password aren't getting set in the state. This is because the action object returned by credChange doesn't get dispatched, so the reducer never get called:
// dispatch calls the reducer which updates the state
dispatch(actionCreator())
// returns an action object, doesn't call reducer
actionCreator()
You can dispatch actions automatically by calling a bound action creator:
// calls the reducer, updates the state
const boundActionCreator = () => {dispatch(actionCreator())}
// call boundActionCreator in your component
boundActionCreator()
mapDispatchToProps can be used to define bound action creators (to be passed as props):
const mapDispatchToProps = (dispatch) => {
return {
credChange: ({ prop, value }) => {dispatch(credChange({prop, value})},
loginUser: ({ empNum, password }) => {dispatch(loginUser({empNum, password})},
logoutUser: () => {dispatch(logoutUser()},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
This should solve the state update issue, allowing props that read from state (empNumber, password, etc.) to update as well.

Resources