Call a function after an asynchronous function finishes executing in React - reactjs

I am pretty new to React and trying to learn by making small, simple applications. I am making a simple React application which has a Login functionality. I am also using Redux store and Redux-saga. My login flow is:
There is a Login component that takes email and password from the user, and on clicking the login button a POST call is made.
email and password are sent to the server, if they are valid the server returns a token in the response which I save in local storage.
If a token is received, action for Login success is fired. Here I set a flag called success: true.
In my front end I check the value of the success flag, and if success==true then I redirect to another page called Customers
Login Component
import React, { Component } from 'react';
import { connect } from "react-redux";
import { withRouter } from 'react-router-dom';
import { loginRequest } from "../../actions/loginActions";
import './styles.css';
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
error: '',
};
}
dismissError = () => {
this.setState({ error: '' });
}
handleSubmit = (evt) => {
evt.preventDefault();
let { email, password } = this.state;
if (!email) {
return this.setState({ error: 'Username is required' });
}
if (!password) {
return this.setState({ error: 'Password is required' });
}
let data = {
email: email,
password: password
}
this.props.login(data); //dispatches a method which then makes the POST call
//the checking happens before the above function has finished executing
if (this.props.success)
this.props.history.push('/customers');
else
return this.setState({
error: 'Invalid Username/Password'
});
}
handleChange = (evt) => {
this.setState({
[evt.target.name]: evt.target.value
});
}
render() {
let { email, password } = this.state;
return (
<form className="loginForm" onSubmit={this.handleSubmit}
action="/upload">
<h2>Login</h2>
{
this.state.error &&
<h3 className='error' onClick={this.dismissError}>
<button onClick={this.dismissError}>✖</button>
{this.state.error}
</h3>
}
<label className="FormFields label">Email</label>
<input type="email" className="FormFields" name="email"
value={email}
onChange={(event) => this.handleChange(event)} />
<br />
<label className="FormFields label">Password</label>
<input type="password" className="FormFields" name="password"
value={password}
onChange={(event) => this.handleChange(event)} />
<br />
<input type="submit" className="FormFields submit"
value="Login" />
</form>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.login.loading,
success: state.login.success
}
}
const mapDispatchToProps = (dispatch) => {
return { login: (data) => {dispatch(loginRequest(data))} }
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login));
Login Saga
import { put, takeEvery, call } from 'redux-saga/effects'
import { LOGIN_REQUEST, LOGIN_PENDING, LOGIN_SUCCESS, LOGIN_FAILURE } from '../actions/loginActions';
export function* login(action) {
const { data } = action.payload;
yield put({ type: LOGIN_PENDING })
let url = 'myserverurl/login'
try {
const response = yield call(fetch, url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
}
});
let tokenObj = yield response.json();
if (response.status === 200) {
localStorage.setItem('user', tokenObj.token);
yield put({ type: LOGIN_SUCCESS, token: tokenObj.token })
}
}
catch (error) {
yield put({ type: LOGIN_FAILURE, error })
}
}
export function* watchLogin() {
yield takeEvery(LOGIN_REQUEST, login)
}
The login reducer is very simple.
Login Reducer
import { LOGIN_REQUEST, LOGIN_PENDING, LOGIN_SUCCESS, LOGIN_FAILURE } from '../actions/loginActions';
const initState = {
loading: false,
success: false,
error: ''
}
const loginReducer = (state = initState, action) => {
switch (action.type) {
case LOGIN_REQUEST:
return {
...state,
loading: false
}
case LOGIN_PENDING:
return {
...state,
loading: true
}
case LOGIN_SUCCESS:
return {
...state,
success: true,
loading: false
}
case LOGIN_FAILURE:
return {
...state,
loading: false,
success: false,
error: action.error
}
default: return state;
}
}
export default loginReducer;
The statement this.props.login(data) in Login Component dispatches the action which then makes a POST call. I want to wait for the entire flow I mentioned above to complete, before it checks the value of success flag, but that doesn't happen.
In the event of a login, how do I wait till the actions of my login reducer are completed before my front end checks for the success flag?? I read the docs on async/await but I didn't really understand how to use them properly. Can anyone help me with this

You cannot immediately check for this.props.success as you are making an async call, you need to add a check for success props in getDerivedStateFromProps
add getDerivedStateFromProps in your Login component
static getDerivedStateFromProps(nextProps, prevState) {
if(!nextProps.loading){
if(nextProps.success === true) {
nextProps.history.push('/customers');
} else {
return { error: 'Invalid Username/Password' }
}
}
return null
}
remove below code from handleSubmit
if (this.props.success)
this.props.history.push('/customers');
else
return this.setState({
error: 'Invalid Username/Password'
});

Related

My this.props are undefined on handleSubmit and other function

I do not know why my this.props are showing undefined on handleSubmit. I have done everything possible. When I log state in mapStateToProps, I see what the action has dispatched, but this.props is not taking it.
This is what I get (https://imgur.com/a/AXixWn9) in logger when I enter the wrong details
I have searched online but no clue. I have debugged this for hours
Login.js
import React from 'react';
import { NavLink } from 'react-router-dom';
import { Redirect } from 'react-router';
import qs from 'qs';
import { connect } from 'react-redux';
import Outer from '../templates/outer';
import history from '../../_helpers/history';
import routes from '../../services/urls';
import apiRequest from '../../services/api';
import loginUser from '../../redux/actions/authentication.action';
import { LOGIN_FAILURE, LOGIN_SUCCESS } from '../../redux/constants/authentication.constants';
import alertActions from '../../redux/actions/alert.actions';
class LoginComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
fields: {},
errors: {},
loginError: '',
submitted: false,
};
const { dispatch } = this.props;
dispatch(alertActions.clear());
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(field, e) {
const { fields } = this.state;
fields[field] = e.target.value;
this.setState({ fields });
}
handleValidation() {
const { fields } = this.state;
const errors = {};
let formIsValid = true;
// Login code
if (!fields.code) {
formIsValid = false;
errors.code = 'This field is required';
}
// Password
if (!fields.password) {
formIsValid = false;
errors.password = 'This field is required';
}
this.setState({ errors });
return formIsValid;
}
handleSubmit(e) {
e.preventDefault();
const { code, password } = this.state.fields;
this.setState({ submitted: true });
if (this.handleValidation()) {
this.setState({ submitted: true });
const payload = {
email: code,
// level: 'SYSTEM',
password,
};
const { dispatch } = this.props;
dispatch(loginUser(payload));
} else {
this.setState({ submitted: false });
}
}
render() {
const { loginError, submitted } = this.state;
return (
<Outer>
<form onSubmit={this.handleSubmit}>
<div className="mt-5" />
{loginError
&& <div className="alert alert-danger">{loginError}</div>
}
<label>Login Code</label>
<input type="text" className="form-control" onChange={this.handleChange.bind(this, 'code')} value={this.state.fields.code || ''} />
<div className="text-danger mt-1 mb-1">{this.state.errors.code}</div>
<br />
<label>Password</label>
<input type="password" className="form-control" onChange={this.handleChange.bind(this, 'password')} value={this.state.fields.password || ''} />
<div className="text-danger mt-1 mb-1">{this.state.errors.password}</div>
<br />
<button
className="btn btn-primary btn-block text-uppercase"
type="submit"
disabled={submitted}
>
{ !submitted ? 'Login to manage my cards' : 'loading...' }
</button>
<div className="row no-gutters mt-4">
<div className="col-md-12">
<NavLink to="/reset-password" className="grey">I have forgotten my password</NavLink>
</div>
</div>
</form>
</Outer>
);
}
}
function mapStateToProps(state) {
console.error(state);
const { auth } = state.authentication;
const { alert } = state.alert;
return { auth, alert };
}
export default connect(mapStateToProps)(LoginComponent);
store.js
export const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware,
loggerMiddleware,
),
);
authentication.reducer.js
function loginReducer(state = {}, action) {
switch (action.type) {
case LOGIN_REQUEST:
return {
user_status: LOGIN_REQUEST,
user_data: action,
};
case LOGIN_SUCCESS:
return {
user_status: LOGIN_SUCCESS,
user_data: action,
};
case LOGIN_FAILURE:
return {
user_status: LOGIN_FAILURE,
user_data: action,
};
default:
return state;
}
}
export default loginReducer;
authentication.action.js
function loginUser(payload) {
function request(user) { return { type: LOGIN_REQUEST, user }; }
function success(response) { return { type: LOGIN_SUCCESS, response }; }
function failure(error) { return { type: LOGIN_FAILURE, error }; }
return (dispatch) => {
// const request = apiRequest(routes.LOGIN, 'POST', qs.stringify(payload));
// const ah = loginUser(qs.stringify(payload));
// console.log(ah);
dispatch(request(payload));
const fetch = apiRequest(routes.LOGIN, 'POST', qs.stringify(payload));
return fetch.then((response) => {
switch (response.status) {
case 400:
dispatch(failure(response));
dispatch(alertActions.error(response.data.description));
break;
case 200:
if (response.data.code === 0) {
localStorage.setItem('qwplx44', JSON.stringify(response.data));
dispatch(success(response.data));
history.push('/dashboard');
break;
}
dispatch(failure(response.data.description));
dispatch(alertActions.error(response.data.description));
break;
default:
return {};
}
}).catch((error) => {
dispatch(failure(error.response.data.message));
dispatch(alertActions.error(error.response.data.message.toString()));
// return false;
});
root-reducer
const rootReducer = combineReducers({
authentication: loginReducer,
alert: alertReducer,
});
export default rootReducer;
You aren't connecting your component correctly. You are trying to destruct keys out of the state that aren't there. I don't see a key called auth on your loginReducer. So this line const { auth } = state.authentication; would return undefined. That is why when logging props auth is undefined.
Instead just pluck what you want from state and you can alias authentication as auth while destructuring :)
const mapStateToProps = ({authentication: auth, alert}) => ({
auth,
alert
})
If you are trying to use the data stored in your reducer after making the request, then you should use the componentDidUpdate lifecycle method
componentDidUpdate(prevProps) {
const { auth: prevAuth } = prevProps
const { auth } = this.props
if (auth.user_status && ((!prevAuth.user_status && auth.user_status) || prevAuth.user_status !== auth.user_status)) {
// TODO handle response from api here
/* ex
if (auth.user_status === LOGIN_FAILURE) {
this.setState({loginFailure: auth.user_data})
}
*/
}
}
Few other things.
You aren't binding handleValidation to the class, but are trying to access state.
You are calling this.setState({ submitted: true }); twice in handleSubmit. Second one is redundant and not needed.
I would refactor your apiRequest to handle the qs.stringify and the error / auth / response status handling, so you dont have to write out that same stuff for every api call :)

Change in Redux state not reflected in component

I am building a prototype that displays a login form. The submit event triggers a lookup from a database. If the lookup fails, I wish to change the form to a) display the error message and b) discard the previous entry for user ID and password.
My reducer changes the state in Redux, but I am not sure how to transfer the data back to the component state.
Here is my form:
import React from 'react';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
export class LoginForm extends React.Component {
constructor(props) {
super(props);
console.log("Login form props", props);
this.state = {
userName: props.user ? props.user.userName : '',
password: props.user ? props.user.password : '',
error: props.error ? props.error : ''
}
}
onUserNameChange = (event) => {
const userName = event.target.value;
this.setState(() => ({ userName }));
};
onPasswordChange = (event) => {
const password = event.target.value;
this.setState(() => ({ password }));
};
onSubmit = (event) => {
event.preventDefault();
if (!this.state.userName || !this.state.password) {
this.setState(() => ({ error: 'User name and password are required.'}));
} else {
this.setState(() => ({ error: '' }));
this.props.onSubmit({
userName: this.state.userName,
password: this.state.password
})
}
};
render() {
console.log("Login form render() this.state", this.state);
// console.log("Login form render() this.props", this.props);
return (
<div>
{this.props.error && <p>{this.props.error}</p>}
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="User name"
autoFocus
value={this.state.userName}
onChange={this.onUserNameChange}
/>
<input
type="password"
placeholder="Password"
value={this.state.password}
onChange={this.onPasswordChange}
/>
<button>Sign In</button>
</form>
<NavLink to="/passwordRecovery" activeClassName="is-active" exact={true}>Forgot Password?</NavLink>
<NavLink to="/newUser" activeClassName="is-active">New User?</NavLink>
</div>
)
}
}
const mapStateToProps = (state) => {
console.log('in LoginForm state.authentication: ', state.authentication);
if (state.authentication.user)
{
return {
error: state.authentication.error,
userName: state.authentication.user.userName,
password: state.authentication.user.password
}
} else {
return {
error: state.authentication.error,
user: state.authentication.user
}
}
}
export default connect(mapStateToProps, undefined)(LoginForm);
Here is the page which displays the form:
import React from 'react';
import { connect } from 'react-redux';
import LoginForm from './LoginForm';
import { login, resetForm } from '../actions/authentication';
export class LoginPage extends React.Component {
onSubmit = (user) => {
console.log('LoginPage onSubmit user: ', user);
console.log('props ', this.props);
this.props.login(user);
if (this.props.user) {
this.props.history.push("/userHome");
}
}
render() {
console.log("LoginPage.render()", this.props)
return (
<div>
<LoginForm
onSubmit={this.onSubmit} error={this.props.error}
/>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
login: (user) => dispatch(login(user)),
resetForm: () => dispatch(resetForm())
});
const mapStateToProps = (state) => {
console.log('state.authentication: ', state.authentication);
return {
error: state.authentication.error,
user: state.authentication.user
};
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
Here is the reducer:
// reducer for authentication actions
const authenticationReducerDefaultState = {
userName: '',
password: ''
};
export default (state = authenticationReducerDefaultState, action) => {
console.log('in reducer, state: ', state);
console.log('in reducer, action: ', action);
switch (action.type) {
case 'LOGIN_REQUEST':
return {
user: action.user,
error: '',
loggedIn: false,
loggingIn: true
};
case 'LOGIN_SUCCESS':
return {
user: action.user,
error: '',
loggedIn: true,
loggingIn: false
}
case 'LOGIN_FAILURE':
return {
user: authenticationReducerDefaultState,
error: action.error,
loggedIn: false,
loggingIn: false
}
case 'LOGOUT':
return {
user: authenticationReducerDefaultState,
error: '',
loggedIn: false,
loggingIn: false
};
default:
return state;
};
};
Here is the action:
import database from '../firebase/firebase';
const request = (user) => ({
type: 'LOGIN_REQUEST',
user
});
const success = (user) => ({
type: 'LOGIN_SUCCESS',
user
});
const failure = (error) => {
// console.log('failure with error ', error);
return {
type: 'LOGIN_FAILURE',
user: { userName: '', password: '' },
error
}};
export const login = (user) => {
return (dispatch) => {
const { userName, password } = user;
// console.log(`login function for ${userName} password ${password}`);
dispatch(request(user));
let matchedUser = undefined;
return database.ref(`users`).once('value').then((snapshot) => {
snapshot.forEach((childSnapshot) => {
const user = childSnapshot.val();
if (user.userName === userName &&
user.password === password) {
matchedUser = user;
};
});
return matchedUser;
}).then((matchedUser) => {
console.log('matched user', matchedUser);
if (matchedUser) {
dispatch(success(user));
} else {
// console.log('dispatching failure');
dispatch(failure(`An error occurred looking up user ID ${userName}`));
};
console.log('end of login function');
});
}
}
// action generator for logout action
export const logout = () => ({
type: 'LOGOUT'
});
Here is my root reducer:
export default () => {
// Store creation
const store = createStore(
combineReducers({
authentication: authenticationReducer
}),
composeEnhancers(applyMiddleware(thunk))
);
return store;
}
I'm hoping someone has already been down this road. Thanks in advance.
The problem is that even though the props change (redux store is updated), you are using the local state inside LoginForm. You map the values to props only once (LoginForm.constructor).
If you want to react to redux store changes, you need to write some code in order to update the local state if something changes on the store.
static getDerivedStateFromProps (props) {
return {
userName: props.user ? props.user.userName : '',
password: props.user ? props.user.password : '',
error: props.error ? props.error : ''
}
}
Whatever you return in this method will end up updating the local component state.
This kind of scenarios is kind of difficult to maintain. You are mixing the concept of controlled and uncontrolled components. You are getting the initial values from props, mapping those to the local state, then handle the state changes locally (when the input changes) but also reacting to changes on the store.
Tip: If you use default props you don't have to check if this.props.user is available.
static defaultProps = {
user: {
userName: '',
password: '''
},
error: ''
}
Have you tried dropping the logic from your mapStateToProps
if (state.authentication.user)
{
return {
error: state.authentication.error,
userName: state.authentication.user.userName,
password: state.authentication.user.password
}
} else {
return {
error: state.authentication.error,
user: state.authentication.user
}
}
}
to:
return {
error: state.authentication.error,
userName: state.authentication.user.userName,
user: state.authentication.user
password: state.authentication.user.password
}

How to implement Firebase authentication with React Redux?

I am using Firebase to handle my authentication through Facebook or google. The thing I really do not understand is the reason why is my store not updated.
Below is an example of my code:
import { createStore } from 'redux';
import {app, facebookProvider, googleProvider } from './../config/config';
const initialState = {
SCREEN_CURRENT: "login",
authenticated: false
}
const reducer = (state = initialState, action) => {
console.log('reducer', action);
switch(action.type){
case "AUTH_LOGIN_FACEBOOK":
state = {
...state,
authenticated: true
}
app.auth().signInWithPopup(facebookProvider)
.then((user, error) => {
if(error){
console.log("Unable to login with Facebook!");
} else {
console.log("Logged in succesfully");
state = Object.assign({}, state, {
authenticated: true
});
}
}).catch((error) => {
if(error){
console.log("Error from Facebook: " + error.message);
}
});
break;
case "AUTH_LOGIN_GOOGLE":
app.auth().signInWithPopup(googleProvider)
.then((user, error) => {
if(error){
console.log("Unable to login with Google!");
} else {
console.log("Logged in succesfully");
return Object.assign({}, state, {
authenticated: true
});
}
}).catch((error) => {
if(error){
console.log("Error from Google: " + error.message);
}
});
break;
default:
break;
}
return state;
}
const store = createStore(reducer);
store.subscribe(() => {
console.log("Store updated", store.getState());
});
export default store;
Can someone explain me why my store is not updated, even though I change my authentication state to true on succesful login (which happened)?
I cannot understand why.
When I click the button that triggers the "AUTH_LOGIN_FACEBOOK" action, the store gets updated. However, not when I change the state of authenticated to true. How do I get the store to update?
There's too much logic inside the reducer. First thing to do here is to move them to an action, or a plain old javascript object, or even a function. Please notice that the code above doesn't take import/export into account, but I think you'll get my point here. Therefore, you have to adapt it to your use case.
// ##########################################################################
// AUTH FUNCTIONS ###########################################################
// ##########################################################################
const FACEBOOK = 'facebook';
const GOOGLE = 'google';
// just to make sure that you'll always return a valid prodiver.
function getProviderObject(provider) {
return {
[FACEBOOK]: new firebase.auth.FacebookAuthProvider(),
[GOOGLE]: new firebase.auth.GoogleAuthProvider()
}[provider] || null;
}
// this function receives a provider object and return a User, no matter what
// provider, Google or Facebook
function signInWithProvider(provider) {
const providerObject = getProviderObject(provider);
if(!providerObject) {
throw new Error('Invalid provider');
}
try {
const response = await FirebaseApp.auth().signInWithPopup(provider);
return response.user;
} catch(error) {
// handle error...
throw error;
}
}
function signInWithFirebase({ email, password }) {
return FirebaseApp.auth().signInAndRetrieveDataWithEmailAndPassword(email, password);
}
// ##########################################################################
// ACTIONS ##################################################################
// ##########################################################################
// this action acts like a router, which will decide to sign in with an
// external provider (GOOGLE or FACEBOOK) or sign in with email/password
const signIn = param => async dispatch => {
try {
const user = await (
[GOOGLE, FACEBOOK].includes(param) ?
signInWithProvider(param) :
signInWithFirebase(param)
);
dispatch({
type: 'CREATE_SESSION',
payload: user
});
return user;
} catch(error) {
console.log(error);
}
};
// ##########################################################################
// REDUCERS #################################################################
// ##########################################################################
const initialState = {
authenticated: false,
user: null,
// etc...
};
function sessionReducer(state = initialState, action = {}) {
switch(action.type) {
case 'CREATE_SESSION': return {
...action.payload,
authenticated: true,
// whatever props you need here...
};
case 'DESTROY_SESSION': return initialState;
default: return state;
}
}
// ##########################################################################
// EXAMPLE ##################################################################
// ##########################################################################
class Auth extends React.Component {
state = {
email: null,
password: null
};
render() {
const { email, password } = this.state;
return (
<p><a href="#" onClick={this.signInWith(FACEBOOK))>SIGN IN WITH FACEBOOK</a></p>
<p><a href="#" onClick={this.signInWith(GOOGLE))>SIGN IN WITH GOOGLE</a></p>
<form onSubmit={this.onSubmit}>
<input
type="email"
onChange={this.onChange('email')}
value={email} placeholder="Email"
/>
<input
type="password"
onChange={this.onChange('email')}
value={password}
placeholder="Password"
/>
<button>Sign In</button>
</form>
)
}
signInWith = provider => event => {
event.preventDefault();
this.props.signIn(provider);
}
onChange = field => value => this.setState({ [field]: value });
onSubmit = () => {
const { email, password } = this.state;
return this.props.signIn({ email, password });
};
}
export default connect(null, { signIn })(Auth);
I hope this helps!

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.

How to pass my error message from server called by axios to my component

I'm really new to React. I have an axios request in my actions I want my error message to pass on the component I have this code :
import axios from 'axios';
import setAuthorizationToken from '../utils/setAuthorizationToken';
import jwtDecode from 'jwt-decode';
import { SET_CURRENT_USER, BASE_URL } from './types';
const instance = axios.create({
baseURL: BASE_URL
});
export function setCurrentUser(user) {
return {
type: SET_CURRENT_USER,
user
};
}
export function logout() {
return dispatch => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
setAuthorizationToken(false);
dispatch(setCurrentUser({}));
}
}
export function login(data) {
return dispatch => {
return instance.post('/authenticate', data).then(function(response) {
const token = response.data.accessToken;
const refreshToken = response.data.refreshToken;
localStorage.setItem('accessToken', token);
localStorage.setItem('refreshToken', refreshToken);
setAuthorizationToken(token);
dispatch(setCurrentUser(jwtDecode(token)));
})
.catch(function(error){
console.log('error: ', error.response.data);
});
}
}
Here is my Component:
import React from 'react';
import TextFieldGroup from '../common/TextFieldGroup';
import validateInput from '../../server/validations/login';
import { connect } from 'react-redux';
import { login } from '../../actions/authActions';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
errors: {},
isLoading: false
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
isValid() {
const { errors, isValid } = validateInput(this.state);
if (!isValid) {
this.setState({ errors });
}
return isValid;
}
onSubmit(e) {
e.preventDefault();
if (this.isValid()) {
this.setState({ errors: {}, isLoading: true });
this.props.login(this.state).then(
(res) => this.context.router.push('/'),
(error) => this.setState({ errors: error.response.data , isLoading: false }),
);
}
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
const { errors, username, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<h1>Login</h1>
{ errors.message && <div className="alert alert-danger">{errors.message}</div> }
<TextFieldGroup
field="username"
label="Username"
value={username}
error={errors.username}
onChange={this.onChange}
/>
<TextFieldGroup
field="password"
label="Password"
value={password}
error={errors.password}
onChange={this.onChange}
type="password"
/>
<div className="form-group"><button className="btn btn-primary btn-lg" disabled={isLoading}>Login</button></div>
</form>
);
}
}
LoginForm.propTypes = {
login: React.PropTypes.func.isRequired
}
LoginForm.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default connect(null, { login })(LoginForm);
Here is the console.log
error: Object {code: "UNAUTHORIZED", message: "Invalid username or password."}
Currently I don't know to pass my error message to component. I'm really new to React and Redux
First you have to add the initial state on reducer. For example
authReducer.js
const initialState = {
... // another state
errors: {}
}
function yourReducer(state = initialState, action) {
case 'SHOW_ERROR':
return {
...state,
errors: action.message
}
default:
return state
}
On login action dispatch the 'SHOW_ERROR'
authActions.js
export function login(data) {
return dispatch => {
return instance.post('/authenticate', data).then(function(response) {
...
// success
})
.catch(function(error){
// fail
dispatch({ type: 'SHOW_ERROR', message: error.response.data })
});
}
}
Then you need to map redux state to be a props on your component
LoginComponent.js
function mapStateToProps(state) {
return {
you: may.return.another.state.here,
errors: state.yourReducerName.errors
}
}
export default connect(mapStateToProps, { login })(LoginForm);
Finally, you can call errors as a props on your Component
class LoginForm extends React.Component {
...
render() {
const { errors, username, password, isLoading } = this.state;
const { errors } = this.props // errors from redux state
return (
<form onSubmit={this.onSubmit}>
<p>{errors.message}</p>
<h1>Login</h1>
...
<div className="form-group"><button className="btn btn-primary btn-lg" disabled={isLoading}>Login</button></div>
</form>
);
}
}
Don't forget to validate the prop types. Good luck!

Resources