简体   繁体   中英

Rendering error messages from failed authentication in React-Redux and React Router DOM v4

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM