Skip to content
Advertisement

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

I’m creating my first React-Redux App. I’m using yo generator-redux and following this repo and official documenation. I have rendered de SignIn Presentational Component and it works fine, show errors if inputs are blanks. The problem is at dispatching. I use Thunk Middleware but the repo doesn’t.

I have used console.log() to explore how deeper is working my code and I found that the Component Actions are being called, the AJAX request (with axios) is working fine, but the .then() (I think) is not working but doesn’t throw errors.

This is my code:

Action

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
    };
}

Component

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;

Container

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;

Reducers

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;

Store

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'));

I hope you can help me! I don’t know if something is wrong because I’m using Thunk and the example don’t, or if something is missging.

Thank you guys!

Advertisement

Answer

I solved my issue. The difference is that I need to process de Promise received from signInUser throug the attribute that has it.

I had to receive the response in response and then access to the Promise in response.payload. In addition, I had to use .then() and .catch() to handle it.

// 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
        });
    });
}
Advertisement