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