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)
Related
i am trying to send the error messages that sent from my server ( express ) to axios and the error message displays in toastify component but the error message doesn't show up here is the login axios function with the toastify how can i display toastify message inside my page from redux ?
here is my code :
// redux controller
const login = async (username, password) => {
await axios.post("/login",{username,password,},
{ withCredentials: true });};
// reducer page
export function generateError(prop) {
return function (dispatch) {
dispatch({
type: "USER_FAIL"
});
toast.error(prop);
};
}
export function generateSuccess(prop) {
return function (dispatch) {
dispatch({
type: "USER_SUCCESS"
});
toast.success(prop);
};
}
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const data = await authService.login(username, password);
if (data) {
if (data.errors) {
const { username, password } = data.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
}
}
return { user: data };
} catch (error) {
console.log(error);
}
}
);
// login page
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password }));
}
i am using react-tostify and #redux-toolkit but the message doesn't display inside my page
i fixed it and here is my code :
// auth.js ( redux page )
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const {data} = await axios.post(
"/login",
{
username,
password,
},
{ withCredentials: true }
);
return { user: data };
} catch (error) {
console.log(error);
}
});
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
const authSlice = createSlice({
name: "auth",
initialState,
extraReducers: {
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
}})
const { reducer } = authSlice; export default reducer;
Login Page :
const { isLoggedIn } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password })).then(data => {
console.log(data)
if (data.payload.user) {
if (data.payload.user.errors) {
const { username, password } = data.payload.user.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
navigate("/dashboard");
}
}
})
}
i realized when i back the data it has an object name payload i used it to get the error messages from express and then i put the message in toastify function gettingError and here it is
const generateError = error => {
toast.error(error, {
position: "bottom-right",
})
}
Hai I'm also looking for the same problem while searching I found a solution at with this : react-toastify-with-redux
my Code : authAction.js
import 'react-toastify/dist/ReactToastify.min.css';
import { toast} from 'react-toastify';
export const registerUser = (userData) => dispatch =>{
axios.post('user/register',userData)
.then(res=>toast.success('Your Account Created Successfully 👍'))
.then(res=> window.location = '/authentication/sign-in')
.catch(err=>dispatch(
{
type: GET_ERRORS,
payload: err.response.data
}
),toast.error("Error 😣"))
// .catch((err)=> {return })
};
On your signUp page just add
<ToastContainer />
That's all ...
This answer is probably late. But I came across this problem and decided to do it my way. I know there is toast. promise to handle promises and I don't want to call dispatch.then every time. So I can up with passing dispatch to my action wrapper. Here is my code.
// utils.ts
type ArgumentTypes<F extends CallableFunction> = F extends (
...args: infer A
) => any
? A[0]
: never;
export const withToast = <T = AnyAction | typeof createAsyncThunk>(
action: T,
{ pending, error, success }: ToastPromiseParams<T>
) => {
return (
dispatch: ReturnType<typeof useAppDispatch>,
actionParams?: ArgumentTypes<T & CallableFunction> | void
) => {
const promise = dispatch(
(action as CallableFunction)(actionParams as any)
).unwrap();
toast.promise(promise, {
pending,
error,
success,
});
};
};
// actions.ts
export const login = createAsyncThunk(
"user/login",
async (payload: {
email: string;
password: string;
}): Promise<Partial<LoginAPIResponse>> => {
const { data } = await axios.post(`${API}/${LOGIN_EP}/`, payload);
return data;
}
);
export const loginWithToast = withToast(login, {
pending: "Logging in...",
error: {
render: (error: any) => {
return error?.password || error?.email
? "Invalid email or password"
: "Something went wrong";
},
},
success: "Logged in successfully",
});
// usage in component
const dispatch = useAppDispatch();
loginWithToast(dispatch, {
email: values.email.value,
password: values.password.value,
});
First createAsyncThunk:
import { coreAxios } from "utilities/axios"; // Own customized axios
import { createAsyncThunk } from "#reduxjs/toolkit";
const BASE_URL = process.env.REACT_APP_MAIN_URL
export const GetProducts = createAsyncThunk(
"inventory/GetProducts",
async () => {
const {data} = await coreAxios.get(`${BASE_URL}/api/product/list/`);
return data
}
);
Second createSlice:
import { createSlice } from "#reduxjs/toolkit";
import { GetProducts } from "services/inventory/product.service";
import { toast } from 'react-toastify';
export const productSlice = createSlice({
name: "products",
initialState: {
productsList: [],
productsLoading: false,
productsError: null,
},
extraReducers:
(builder) => {
builder.addCase(GetProducts.pending, (state) => {
toast.loading('Promise is pending...')
state.productsLoading = true
});
builder.addCase(GetProducts.fulfilled, (state, action) => {
toast.dismiss();
toast.success('Promise resolved 👌');
state.productsList = action.payload
state.productsLoading = false
state.productsError = null
});
builder.addCase(GetProducts.rejected, (state, action) => {
toast.dismiss();
toast.error('Promise rejected 🤯 😣')
state.productsLoading = false
state.productsError = action.error?.message
});
},
});
export default productSlice.reducer;
Third page:
import { ToastContainer } from 'react-toastify';
import { useSelector, useDispatch } from "react-redux";
import { GetProducts } from 'services/inventory/product.service';
const Product = () => {
const { productsList, productsLoading, productsError } = useSelector((state) => state.products);
const dispatch = useDispatch();
useEffect(() => {
dispatch(GetProducts());
}, []);
return (
<div className="grid crud-demo">
<h1>Hello Alim</h1>
<ToastContainer />
</div>
);
}
I'm not good in react
but i code))
I have problem. My flow:
customer open "create new user" page -> then create user -> then redirect "Success" page.
after that when customer go to "create new user" page again, -> he go "Success" page without creating.
I know it is because i use props wrong way
how can I fix it?
my Component.js
class addUser extends Component {
handleSubmit(e) {
e.preventDefault();
const user = {
name: this.state.name,
}
this.props.newUser(user);
}
componentDidUpdate(prevProps, prevState){
const { isCreateNewUser } = this.props.isCreateNewUser;
if(isCreateNewUser == true) {
this.props.history.push('/')
}
}
render() {
<form className="add-new-post" autoComplete="off" onSubmit={ this.handleSubmit }>
</form>
}
my reducer.js
export const reducerSubscriptions = function(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return {
...state,
isCreateNewUser: action.payload
}
}
}
my action.js
export const newUser = (subscription) => dispatch => {
axios.post('/api/user/add', subscription)
.then(res => {
dispatch({
type: ADD_USER,
payload: res.data
});
});
}
your problem seems like a props or state that don't return to his initial value
and
const { isCreateNewUser } = this.props.isCreateNewUser;
isCreateNewUser his an object? because when you destruct a non-object it should looks like this
const { isCreateNewUser } = this.props;
and where do you update isCreateNewUser to true
my solution:
my action.js
export const createUser = (user) => async dispatch => {
const res = await axios.post('/api/user/add', subscription);
dispatch({ type: SOME_ACTION_TYPE, payload: res });
return res;
}
my Component.js
this.props.createUser(user)
.then(data => {
if(data.data[0]){
this.props.history.push('/');
}
}).catch(error => {
console.log("error", error);
})
i don't know is it good or not but it works.
thanks
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'm a PHP/Laravel mid-level developer. I'm totally a noob when it comes to react.js. Now I know that react uses API's and stuff to show data when it comes to laravel backend. I am used to traditional HTML, CSS, JS, Bootstrap, Ajax and whatsoever. I have a simple task to login a user from react through laravel backend. I'v created the APIs for that task and they're working totally fine and somehow I got lucky and attached those APIs ALL BY MYSELF (with a little research, of course). Now whenever I try to signin, I receive the usual data through an axios request to backend and have it in a variable signInUser. Now when I try to pass that data to other action function it's going undefined somehow. Here's my code so that you can understand what I'm trying to achieve:
Components\SignIn.js
constructor() {
super();
this.state = {
email: '',
password: ''
}
}
componentDidUpdate() {
if (this.props.showMessage) {
setTimeout(() => {
this.props.hideMessage();
}, 100);
}
if (this.props.authUser !== null) {
this.props.history.push('/');
}
}
render() {
const {email, password} = this.state;
const {showMessage, loader, alertMessage} = this.props;
return (
// other components and stuff...
<Button onClick={() => { this.props.showAuthLoader(); this.props.userSignIn({email, password});}} variant="contained" color="primary">
<IntlMessages id="appModule.signIn"/>
</Button>
);
}
const mapStateToProps = ({auth}) => {
const {loader, alertMessage, showMessage, authUser} = auth;
return {loader, alertMessage, showMessage, authUser}
};
export default connect(mapStateToProps, {
userSignIn,
hideMessage,
showAuthLoader
})(SignIn);
Sagas\Auth.js
const signInUserWithEmailPasswordRequest = async (email, password) =>
await axios.post('auth/login', {email: email, password: password})
.then(authUser => authUser)
.catch(err => err);
function* signInUserWithEmailPassword({payload}) {
const {email, password} = payload;
try {
const signInUser = yield call(signInUserWithEmailPasswordRequest, email, password);
if (signInUser.message) {
yield put(showAuthMessage(signInUser.message));
} else {
localStorage.setItem('user_id', signInUser.data.user.u_id);
yield put(userSignInSuccess(signInUser.data.user.u_id));
}
} catch (error) {
yield put(showAuthMessage(error));
}
}
export function* signInUser() {
yield takeEvery(SIGNIN_USER, signInUserWithEmailPassword);
}
export default function* rootSaga() {
yield all([fork(signInUser),
// couple of other functions...
);
}
actions\Auth.js
export const userSignIn = (user) => {
return {
type: SIGNIN_USER,
payload: user
};
};
export const userSignInSuccess = (authUser) => {
console.log(authUser); // It's printing undefined, I don't know why?!
return {
type: SIGNIN_USER_SUCCESS,
payload: authUser
}
};
reducers\Auth.js
const INIT_STATE = {
loader: false,
alertMessage: '',
showMessage: false,
initURL: '',
authUser: localStorage.getItem('user_id'),
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case SIGNIN_USER_SUCCESS: {
return {
...state,
loader: false,
authUser: action.payload
}
}
case INIT_URL: {
return {
...state,
initURL: action.payload
}
}
default:
return state;
}
}
P.s: It's a purchased react.js template (not my code).
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.