Dispatch() 调用了一个函数,但是 .then() 在 React-Redux 上不起作用

Dispatch() calls a function but .then() doesn't work on React-Redux

我正在创建我的第一个 React-Redux 应用程序。我正在使用 yo generator-redux and following this repo 和官方文档。我已经渲染了 de SignIn Presentational Component,它工作正常,如果输入为空白则显示错误。问题出在派送上。我使用 Thunk 中间件,但 repo 没有。

我已经使用 console.log() 来探索我的代码工作的深度,我发现正在调用组件操作,AJAX 请求(使用 axios)工作正常,但是 .then()(我认为)不工作但不会抛出错误。

这是我的代码:

动作

actions/UsersActions.js

import axios from 'axios';

//sign in user
export const SIGNIN_USER = 'SIGNIN_USER';
export const SIGNIN_USER_SUCCESS = 'SIGNIN_USER_SUCCESS';
export const SIGNIN_USER_FAILURE = 'SIGNIN_USER_FAILURE';

//Get current user(me) from token in localStorage
export const ME_FROM_TOKEN = 'ME_FROM_TOKEN';
export const ME_FROM_TOKEN_SUCCESS = 'ME_FROM_TOKEN_SUCCESS';
export const ME_FROM_TOKEN_FAILURE = 'ME_FROM_TOKEN_FAILURE';
export const RESET_TOKEN = 'RESET_TOKEN';

//log out user
export const LOGOUT_USER = 'LOGOUT_USER';

axios.defaults.baseURL = location.href.indexOf('10.1.1.33') > 0 ? 'http://10.1.1.33:8080/api/v1' : 'http://10.1.1.33:8080/api/v1';

export function signInUser(formValues) {
    const request = axios.post('/login', formValues);
    console.log(request); 
    // It works fine and receives the resposen when is invoked from Container
    return {
        type: SIGNIN_USER,
        payload: request
    };
}

export function signInUserSuccess(user) {
    return {
        type: SIGNIN_USER_SUCCESS,
        payload: user
    }
}

export function signInUserFailure(error) {
    return {
        type: SIGNIN_USER_FAILURE,
        payload: error
    }
}

export function meFromToken(tokenFromStorage) {
  //check if the token is still valid, if so, get me from the server
  const request = axios.get('/me/from/token?token=${tokenFromStorage}');

  return {
    type: ME_FROM_TOKEN,
    payload: request
  };
}

export function meFromTokenSuccess(currentUser) {
  return {
    type: ME_FROM_TOKEN_SUCCESS,
    payload: currentUser
  };
}

export function meFromTokenFailure(error) {
  return {
    type: ME_FROM_TOKEN_FAILURE,
    payload: error
  };
}


export function resetToken() {//used for logout
  return {
    type: RESET_TOKEN
  };
}

export function logOutUser() {
    return {
        type: LOGOUT_USER
    };
}

组件

components/SignInForm.js

import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';

class SignInForm extends Component {
    static contextTypes = {
        router: PropTypes.object
    };

    componentWillUnmount() {
        // Invoked immediately before a component is unmounted from the DOM.
        // Perform any necessary cleanup in this method, such as invalidating timers or 
        // cleaning up any DOM elements that were created in componentDidMount.

        // Important! If your component is navigating based on some global state(from say componentWillReceiveProps)
        // always reset that global state back to null when you REMOUNT
        this.props.resetMe();
    }

    componentWillReceiveProps(nextProps) {
        // Invoked when a component is receiving new props. This method is not called for the initial render.
        if(nextProps.user && nextProps.user.status === 'authenticated' && nextProps.user.user && !nextProps.user.error) {
            this.context.router.push('/');
        }

        //error
        //Throw error if it was not already thrown (check this.props.user.error to see if alert was already shown)
        //If u dont check this.props.user.error, u may throw error multiple times due to redux-form's validation errors
        if(nextProps.user && nextProps.user.status === 'signin' && !nextProps.user.user && nextProps.user.error && !this.props.user.error) {
            alert(nextProps.user.error.message);
        }
    }

    render() {
        const { asyncValidating, fields: { email, password }, handleSubmit, submitting, user } = this.props;

        return (
            <div>
                <form onSubmit={handleSubmit(this.props.signInUser.bind(this))}>
                    <div>
                        <label>Email</label>
                        <input type="text" placeholder="email@4geeks.com.ve"  {...email} />
                        <div>{email.touched ? email.error : ''}</div>
                        <div>{ asyncValidating === 'email' ? 'validating...' : ''}</div>
                    </div>
                    <div>
                        <label>Password</label>
                        <input type="password"  {...password} />
                        <div>{password.touched ? password.error : ''}</div>
                        <div>{ asyncValidating === 'password' ? 'validating...' : ''}</div>
                    </div>
                    <button type="submit" disabled={submitting}>Submit</button>
                </form>
            </div>
        );
    }
}

export default SignInForm;

容器

containers/SignInFormContainer.js

import { reduxForm } from 'redux-form';
import SignInForm from '../components/SignInForm';
import { signInUser, signInUserSuccess, signInUserFailure } from '../actions/UsersActions';

// Client side validation
function validate(values) {
    var errors = {};
    var hasErrors = false;
    if(!values.email || values.email.trim() == '') {
        errors.email = "Enter a registered email.";
        hasErrors = true;
    }
    if(!values.password || values.password.trim() == '') {
        errors.password = "Enter password.";
        hasErrors = true;
    }
    return hasErrors && errors;
}

// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
    return new Promise ((resolve, reject) => {
        console.log('this is showed');
        dispatch(signInUser(values))
        .then((response) =>  {
            console.log('this console.log is not showed');
            let data = response.payload.data;
            // if any one of these exist, then there is a field error 
            if(response.payload.status != 200) {
                // let other components know of error by updating the redux` state
                dispatch(signInUserFailure(response.payload));
                reject(data); // this is for redux-form itself
            } else {
                // store JWT Token to browser session storage 
                // If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
                // sessionStorage = persisted only in current tab
                sessionStorage.setItem('dhfUserToken', response.payload.data.token);
                // let other components know that we got user and things are fine by updating the redux` state 
                dispatch(signInUserSuccess(response.payload)); 
                resolve(); // this is for redux-form itself
            }
        });
    });
}

const mapDispatchToProps = (dispatch) => {
    return {
        signInUser: validateAndSignInUser
    }
}

function mapStateToProps(state, ownProps) {
    return { 
        user: state.user
    };
}

// connect: first argument is mapStateToProps, 2nd is mapDispatchToProps
// reduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps
export default reduxForm({
    form: 'SignInForm', 
    fields: ['email', 'password'], 
    null,
    null,
    validate 

}, mapStateToProps, mapDispatchToProps)(SignInForm);

Presentational/Page/View

presentational/SignIn.js

import React, { Component } from 'react';
import HeaderContainer from '../containers/HeaderContainer';
import SignInFormContainer from '../containers/SignInFormContainer';

class SignIn extends Component {
  render() {
    return (
      <div>
        <HeaderContainer />
        <SignInFormContainer />
      </div>
    );
  }
}

export default SignIn;

减速机

reducres/UserReducer.js

import {
    ME_FROM_TOKEN, ME_FROM_TOKEN_SUCCESS, ME_FROM_TOKEN_FAILURE, RESET_TOKEN,
    SIGNIN_USER, SIGNIN_USER_SUCCESS,  SIGNIN_USER_FAILURE,
    LOGOUT_USER
} from '../actions/UsersActions';

const INITIAL_STATE = {user: null, status:null, error:null, loading: false};

export default function(state = INITIAL_STATE, action) {
  let error;
  switch(action.type) {
    case ME_FROM_TOKEN:// loading currentUser("me") from jwttoken in local/session storage storage,
        return { ...state, user: null, status:'storage', error:null, loading: true}; 
    case ME_FROM_TOKEN_SUCCESS://return user, status = authenticated and make loading = false
        return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
    case ME_FROM_TOKEN_FAILURE:// return error and make loading = false
        error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors   
        return { ...state, user: null, status:'storage', error:error, loading: false};
    case RESET_TOKEN:// remove token from storage make loading = false
        return { ...state, user: null, status:'storage', error:null, loading: false};

    case SIGNIN_USER:// sign in user,  set loading = true and status = signin
        return { ...state, user: null, status:'signin', error:null, loading: true}; 
    case SIGNIN_USER_SUCCESS://return authenticated user,  make loading = false and status = authenticated
        return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
    case SIGNIN_USER_FAILURE:// return error and make loading = false
        error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors      
        return { ...state, user: null, status:'signin', error:error, loading: false};

    case LOGOUT_USER:
      return {...state, user:null, status:'logout', error:null, loading: false};

    default:
        return state;
  }
}

reducers/index.js

import { combineReducers } from 'redux';
import { UserReducer } from './UserReducer';
import { reducer as formReducer } from 'redux-form';

const rootReducer = combineReducers({
    user: UserReducer,
    form: formReducer  // <-- redux-form
});

export default rootReducer;

商店

import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
import thunkMiddleware from 'redux-thunk';
import {devTools, persistState} from 'redux-devtools';
import rootReducer from '../reducers/index';

let createStoreWithMiddleware;

// Configure the dev tools when in DEV mode
if (__DEV__) {
  createStoreWithMiddleware = compose(
    applyMiddleware(thunkMiddleware),
    devTools(),
    persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
  )(createStore);
} else {
  createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
}

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState);
}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import configureStore from './store/configureStore';
import {renderDevTools} from './utils/devTools';

const store = configureStore();

ReactDOM.render(
      <div>
        {/* <Home /> is your app entry point */}
        <Provider store={store}>
          <Router history={browserHistory} routes={routes} />
        </Provider>

        {/* only renders when running in DEV mode */
          renderDevTools(store)
        }
      </div>
    , document.getElementById('main'));

希望你能帮帮我!我不知道是否有问题,因为我正在使用 Thunk 而示例没有,或者是否缺少某些内容。

谢谢大家!

看起来你在使用 redux-thunk 而我在使用 redux-promise 中间件。他们是完全不同的。如果你想使用 repo

,你应该将 redux-thunk 更改为 redux-promise

我解决了我的问题。不同之处在于我需要通过具有它的属性处理从 signInUser 接收到的 de Promise。

我必须在 response 中收到响应,然后在 response.payload 中访问 Promise。此外,我不得不使用 .then().catch() 来处理它。

// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
    return new Promise ((resolve, reject) => {
        let response = dispatch(signInUser(values));
        response.payload.then((payload) =>  {
            // if any one of these exist, then there is a field error 
            if(payload.status != 200) {
                // let other components know of error by updating the redux` state
                dispatch(signInUserFailure(payload));
                reject(payload.data); // this is for redux-form itself
            } else {
                // store JWT Token to browser session storage 
                // If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
                // sessionStorage = persisted only in current tab
                sessionStorage.setItem('dhfUserToken', payload.data.token);
                // let other components know that we got user and things are fine by updating the redux` state 
                dispatch(signInUserSuccess(payload)); 
                resolve(); // this is for redux-form itself
            }
        }).catch((payload) => {
            // let other components know of error by updating the redux` state
            sessionStorage.removeItem('dhfUserToken');
            dispatch(signInUserFailure(payload));
            reject(payload.data); // this is for redux-form itself
        });
    });
}