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
}
Related
Here after successfull login im directing to homepage. But how can show whether the user is logged in or loggedout in homepage.here i have attached my codesanbox link.
https://codesandbox.io/s/inspiring-rain-olm7wx?file=/src/components/Home.component.jsx
import axios from 'axios';
export const loginAction = {
login,
};
function login(user) {
return (dispatch) => {
var data = {
email: user.email,
password: user.password,
};
axios
.post('https://reqres.in/api/login', data)
.then((res) => {
console.log("res", (res));
alert("response " + JSON.stringify(res.data));
dispatch(setUserLogin(res.data, false));
localStorage.setItem("isLogin", true)
window.location.pathname = "./";
})
.catch(err => {
dispatch(setUserLoginError(err, true));
alert("error" + err);
});
};
}
export function setUserLogin(token, showError) {
return {
type: 'SET_LOGIN_SUCCESS',
token: token,
isLoading: false,
showError: showError,
};
}
export function setUserLoginError(error, showError) {
return {
type: 'SET_LOGIN_ERROR',
error: error,
showError: showError,
};
}
you are working with redux so following code will help you to confirm user logged in or logged out
import React from "react";
import { useSelector } from 'react-redux'
export default function Home() {
// https://react-redux.js.org/api/hooks
const isLogin = useSelector((state) => state.login.isLogin)
console.log('isLogin ',isLogin);
return (
<>
<div>This is homepage</div>
<br />
<a href="/login">
<button> LOGIN</button>
</a>
</>
);
}
Here useSelector hook available from react-redux which you already have in your project. And isLogin boolean value you can read from redux store if it is true means user logged in and it it is false means user not logged in.
You can set this isLogin from your login component using reducer.
Update 1
In your reducer I can see you have not set isLogin set it as following:
const initialState = {
token: '',
showError: false,
error: '',
isLogin: false,
isLoading: false
};
export function login(state = initialState, action) {
switch (action.type) {
case 'SET_LOGIN_SUCCESS':
return {
...state,
token: action.token,
isLogin: true,
isLoading: action.isLoading,
showError: action.showError,
};
case 'SET_LOGIN_ERROR':
return {
...state,
error: action.error,
showError: action.showError,
isLogin: false,
};
default:
return state;
}
}
Update 2
You are getting isLogin false in App.js because you of window.location.pathname = "./"; in loginAction.js It will reload the page and so isLogin will reset so remove it first.
Instead you can try following code in Login.jsx
import { useNavigate } from "react-router-dom";
import { useSelector } from 'react-redux'
function Login(props) {
let navigate = useNavigate();
const isLogin = useSelector((state) => state.login.isLogin)
useEffect(()=>{
if(isLogin){
navigate('/')
}
},[isLogin])
}
I have created an app using react native typescript and redux. I have successfully configured the store and everything. But the problem is when I tried to login, I dispatch payload to authReducer and it returns undefined.
store.tsx
import AsyncStorage from "#react-native-async-storage/async-storage";
import { createStore, applyMiddleware } from "redux";
import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from "redux-persist";
import rootReducer from "../_redux/reducers/index";
const persistConfig = {
key: "root",
storage: AsyncStorage,
whitelist: ["authReducer"],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, applyMiddleware(createLogger()));
let persistor = persistStore(store);
export { store, persistor };
authReducer.tsx
export const initState = {
isAuthenticated: false,
user: {},
token: "No Token",
};
const rootReducer = (state = initState, action: any) => {
if (action.type === "LOG_IN") {
const { isAuthenticated, user, token } = action.payload;
return {
...state,
isAuthenticated,
user,
token,
};
}
if (action.type === "LOG_OUT") {
const { isAuthenticated, user, token } = action.payload;
return {
...state,
isAuthenticated,
user,
token,
};
}
if (action.type === "UPDATE_PROFILE") {
return {
...state,
user: {
...state.user,
name: action.name,
email: action.email,
phone: action.phone,
},
};
}
return state;
};
export default rootReducer;
Login.tsx
axios
.post(`${BASE_URL}/login`, {
email: email,
password: password,
})
.then((response) => {
setLoading(false);
if (response.data.success) {
// response.data.user_data = {user_id: 1, user_name: "Jithin Varghese", user_email: "jithin#gmail.com", user_phone: "1234567890"}
// response.data.token = 1
logIn(true, response.data.user_data, response.data.token);
navigation.navigate("Home");
}
})
.catch((error) => {
console.log(error);
setLoading(false);
});
};
const mapDispatchToProps = (dispatch: any) => ({
logIn: ({ isAuthenticated, user, token }: any) => {
dispatch({
type: "LOG_IN",
payload: {
isAuthenticated,
user,
token,
},
});
},
});
const mapStateToProps = (state: any) => ({
isAuthenticated: state.authReducer.isAuthenticated,
user: state.authReducer.user,
token: state.authReducer.token,
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
The isAuthenticated, user, token is always undefined after dispatch. You can check the below screenshot for more details.
Initial load
After login axios call dispatch (logIn(true, response.data.user_data, response.data.token))
I have tried a lot to find a solution and I couldn't find any. What is the problem. I cannot figure this out.
I think prev state is causing the problem. It is empty initially.
I think you're passing 3 args to "logIn" instead of 1 arg as an object
instead of calling it like: logIn(true, response.data.user_data, response.data.token);
it should be like
logIn({isAuthenticated:true, user:response.data.user_data, token:response.data.token});
You are not passing data correctly in Action. You need to pass one argument and you are passing three arguments.
You need to pass data like this
logIn(isAuthenticated:true , user: response.data.user_data, token: response.data.token)
I'm creating to do list app so I created API using laravel and it works fine . The scenario is when user login I dispatch an action to check his email and password then update the store in redux with the user tasks . The tasks reach to the reducer fine but I cannot get it in the TasksComponent I get undefined when console.log(this.props.tasks) or even console.log(this.props.isLoading)
The reducer login
import * as ActionTypes from './ActionTypes';
export const Login = (state = {
tasks: [],
categories: [],
error: null,
isLoading: true
}, action) => {
switch(action.type) {
case ActionTypes.AUTH_LOADING:
return {...state, tasks:[], categories:[], error: null, isLoading: true}
case ActionTypes.AUTH_SUCCESS:
console.log(action.payload.user.tasks)
return {...state, tasks: action.payload.user.tasks, categories: action.payload.user.categories,error: null, isLoading: false}
case ActionTypes.AUTH_FAIL:
return {...state, tasks:[], categories:[], error: action.error, isLoading: false}
default:
return state;
}
}
The Action creator
import * as ActionTypes from './ActionTypes';
import { baseUrl } from '../shared/baseUrl';
export const authSuccess = (authData) => ({
type: ActionTypes.AUTH_SUCCESS,
payload: authData
});
export const authFail = (error) => ({
type: ActionTypes.AUTH_FAIL,
error: error
});
export const authLoading = () =>({
type: ActionTypes.AUTH_LOADING
})
export const auth = (email, password) => dispatch => {
dispatch(authLoading());
const user = {
email: email,
password: password
}
return fetch('http://localhost:8000/api/users/login',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user)
})
.then(data => data.json())
.then(user => {
dispatch(authSuccess(user));
})
.catch(error => dispatch(authFail(error)));
}
The tasks component
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { Loading } from './LoadingComponent';
class Tasks extends Component {
constructor(props) {
super(props);
}
render() {
if(this.props.isLoading) {
<Loading />
} else {
return(<h1>Yes</h1>)
}
}
}
const mapStateToProps = state =>{
return {
isLoading: state.isLoading // Always get undefined
}
}
export default connect(mapStateToProps)(Tasks);
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}`);
}
};
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.