TL;DR version: In a React-Redux project with React Router DOM v4 how do I take the server response from an axios.put
in an ./actions
file and import into my "smart components" in ./containers
so it can be used in auth logic and rendered as messages for the user (eg, "Invalid credentials").
Long version: So I have been learning React-Redux and started with the Stephen Grider courses on Udemy. What is adding to my confusion stems from the fact that the intro/intermediate course is in React Router DOM v4
and the advanced course is in React Router v2
. Spending a lot of time converting over to v4
.
Anyway, getting protected routes and rerouting after successful login stuff sorted at. The problem I have now is getting responses from the server into a Redux-React container.
For example, if the login credentials are wrong, the login API responds with a 400 error. I should be able to take this response and render a message like The credentials provided are invalid
. I can get the response in my ./actions/authentication
file and console.log()
it, but just can't get it into the ./containers/authentication/signin.js
.
The Stephen Grider tuts using React Router v2
work fine. Here is the code from his project, followed by mine:
// ./actions/index.js
import axios from 'axios';
import { browserHistory } from 'react-router';
import {
AUTH_USER,
UNAUTH_USER,
AUTH_ERROR,
FETCH_MESSAGE
} from './types';
const ROOT_URL = 'http://localhost:3090';
export function signinUser({ email, password }) {
return function(dispatch) {
axios.post(`${ROOT_URL}/signin`, { email, password })
.then(response => {
dispatch({ type: AUTH_USER });
localStorage.setItem('token', response.data.token);
// The below no longer works as of React Router DOM v4
browserHistory.push('/feature');
})
.catch(() => {
dispatch(authError('Bad Login Info'));
});
}
}
export function authError(error) {
return {
type: AUTH_ERROR,
payload: error
};
}
// ./components/auth/signin.js
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import * as actions from '../../actions';
class Signin extends Component {
handleFormSubmit({ email, password }) {
// Need to do something to log user in
this.props.signinUser({ email, password });
}
renderAlert() {
if (this.props.errorMessage) {
return (
<div className="alert alert-danger">
<strong>Oops!</strong> {this.props.errorMessage}
</div>
);
}
}
render() {
const { handleSubmit, fields: { email, password }} = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<fieldset className="form-group">
<label>Email:</label>
<input {...email} className="form-control" />
</fieldset>
<fieldset className="form-group">
<label>Password:</label>
<input {...password} type="password" className="form-control" />
</fieldset>
{this.renderAlert()}
<button action="submit" className="btn btn-primary">Sign in</button>
</form>
);
}
}
function mapStateToProps(state) {
return { errorMessage: state.auth.error };
}
export default reduxForm({
form: 'signin',
fields: ['email', 'password']
}, mapStateToProps, actions)(Signin);
// ./reducers/index.js
import {
AUTH_USER,
UNAUTH_USER,
AUTH_ERROR,
FETCH_MESSAGE
} from '../actions/types';
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import authReducer from './auth_reducer';
const rootReducer = combineReducers({
form,
auth: authReducer
});
export default rootReducer;
export default function(state = {}, action) {
switch(action.type) {
case AUTH_USER:
return { ...state, error: '', authenticated: true };
case UNAUTH_USER:
return { ...state, authenticated: false };
case AUTH_ERROR:
return { ...state, error: action.payload };
case FETCH_MESSAGE:
return { ...state, message: action.payload };
}
return state;
}
Mine is just slightly modified due to trying to get this working with React Router DOM v4
. I put comments in the code that may be of some interest. The ./reducers
have been unchanged except for import... from
.
// ./actions/authentication/index.js
import axios from 'axios';
import { ROOT_URL } from '../../../config/settings/secrets.json';
const AUTH_USER = 'auth_user';
const UNAUTH_USER = 'unauth_user';
const AUTH_ERROR = 'auth_error';
export function signinUser({ username, password }) {
return function(dispatch) {
axios.post(`${ROOT_URL}/api/auth/token/`, { username, password })
.then(response => {
dispatch({ type: AUTH_USER });
localStorage.setItem('token', response.data.token);
})
.catch(() => {
dispatch(authError('Bad Login Info'));
});
}
}
export function authError(error) {
// Can console.log() this out and payload does show 'Bad Login Info'
// How to get it innto the signin.js container though?
return {
type: AUTH_ERROR,
payload: error
};
}
// ./containers/authentication/signin.js
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import * as actions from '../../actions/authentication';
const renderInput = field => {
const { input, type } = field;
return (
<div>
<input {...input} type={type} className='form-control' />
</div>
);
}
class Signin extends Component {
handleFormSubmit({ username, password }) {
this.props.signinUser({ username, password });
// This is a bad idea since it will pass anything if username and password is entered
// Thus why trying to take the server response and use that
if (username !== undefined && password !== undefined) {
this.props.history.push('/home');
}
}
renderAlert(errorMessage) {
// Cannot console.log() any of this out
// this.renderAlert() doesn't seem to be getting triggered
if (errorMessage) {
return (
<div className="alert alert-danger">
<strong>Oops!</strong> {this.props.errorMessage}
</div>
);
}
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<div className="form-group">
<label>Username:</label>
<Field name='username' type='username' component={renderInput} />
</div>
<div className="form-group">
<label>Password:</label>
<Field name='password' type='password' component={renderInput} />
</div>
{this.renderAlert()}
<button action="submit" className="btn btn-primary">Sign in</button>
</form>
);
}
}
function mapStateToProps(state) {
console.log(state);
return { errorMessage: state.auth.error };
}
Signin = connect(mapStateToProps, actions)(Signin);
Signin = reduxForm({
form: 'signin'
})(Signin);
export default withRouter(Signin);
Edit: Consulting this image as refresher for the React-Redux data flow. Seems like most everything is working up to getting it back into the container
, so is mapStateToProps
failing somehow?
Oh man, I wasn't exporting my action types ( AUTH_USER
, UNAUTH_USER
, and AUTH_ERROR
) so that they could properly be consumed by the reducers
.
// ./actions/authentication/index.js
export const AUTH_USER = 'auth_user';
export const UNAUTH_USER = 'unauth_user';
export const AUTH_ERROR = 'auth_error';
The missing export
caused the issue.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.