Simon Déchamps Simon Déchamps - 3 months ago 16
React JSX Question

React/Redux: how to hold login fail

I'm very beginner in React/Redux and I was trying to do something in an already existing code, just to understand how it works. The part I want to edit is the connection part. As it's now, if the login and password are OK, you go to a new page, and if not, it does nothing.

The only simple thing I'm trying to do is to show the user their login informations are wrong, by adding a red border on the fields for example.

So here is the code I added, I'll try not to show you useless code and not to forget useful code, but let me know if you need more.

The first thing I did is adding a constant for the error in actionTypes.js:

export const AUTH_REQUEST = 'AUTH_REQUEST';
export const AUTH_RECEIVE = 'AUTH_RECEIVE';
export const AUTH_ERROR = 'AUTH_ERROR';


Then in actions/auth.js, I added the authError function and called it after a fail response from the server:

function authRequest() {
return {
type: actionTypes.AUTH_REQUEST
};
}

function authReceive(authToken) {
return {
type: actionTypes.AUTH_RECEIVE,
authToken
};
}

function authError() {
return {
type: actionTypes.AUTH_ERROR
};
}

export function fetchLogin(email, password) {
return function (dispatch) {
dispatch(authRequest());

const urlApi = `//${AUTH_BACKEND_HOST}:${AUTH_BACKEND_PORT}/${AUTH_BACKEND_URL.login}`
fetch(urlApi, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
if(response.ok) {
// SUCCESS
response.json().then(function(json) {
dispatch(authReceive(json.key));
dispatch(push('/'));
});
} else {
// FAIL
response.json().then(function(json) {
dispatch(authError());
});
}
})
.catch(function(ex) {
console.log(ex);
});
};
}


Now, in reducers/auth.js:

const initialState = {
authToken: '',
isFetching: false,
error: false,
errorMessage: ''
}

export default function (state=initialState, action) {
switch (action.type) {
case actionType.AUTH_REQUEST:
return {
...state,
isFetching: true
};
case actionType.AUTH_RECEIVE:
return authLogin(state, action);
case actionType.AUTH_ERROR:
return {
...state,
error: true,
errorMessage: 'Incorrect login or password!'
};
}
return state;
}

function authLogin(state, action) {
const { authToken } = action;
return {
...state,
isFetching: false,
authToken
};
}


Until now, it seems to work when I inspect it in Firefox. The state contains the error and errorMessage values.

So here is my components/Login/presenter.jsx which I thought was going to display the right HTML depending on the state:

import React from 'react';

const Login = React.createClass({

handleSubmit(event) {
event.preventDefault()

const email = this.refs.email.value
const password = this.refs.password.value

this.props.onAuth(email, password);
},

render() {
const { errorMessage } = this.props

return (
<form onSubmit={this.handleSubmit}>
<label>Email <input ref="email" placeholder="email" required /></label>
<label>Password <input ref="password" placeholder="password" type="password" required /></label><br />
<p>{errorMessage}</p>
<button type="submit">Login</button>
</form>
)
}
});

export default Login;


And here is components/Login/index.js which I think imports the presenter and do... things...:

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Login from './presenter';

function mapDispatchToProps(dispatch) {
return {
onAuth: bindActionCreators(actions.fetchLogin, dispatch)
};
}

export default connect(null, mapDispatchToProps) (Login);





Edit : it seems that one of the problems is that I'm not mapping the state to props. I tried Mael Razavet and azium's answers, adding mapStateToProps in Login/index.js:

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Login from './presenter';

function mapDispatchToProps(dispatch) {
return {
onAuth: bindActionCreators(actions.fetchLogin, dispatch)
};
}

function mapStateToProps (state) {
return {
errorMessage: state.errorMessage
};
}

export default connect(mapStateToProps, mapDispatchToProps) (Login);


But it seems that errorMessage is still undefined.

Thank you.

Answer

I think you forgot to map your state to props. In your case, you should add this content to your components/Login/index.js:

import * as actions from './actions/auth.js';
import Login from './presenter';

const mapStateToProps = (state) => {
  return {
    error: state.login.error,
    errorMessage: state.login.errorMessage,
  };
};
const mapDispatchToProps = (dispatch) => {
  return {
    onAuth: (email, password) => {
      dispatch(actions.fetchLogin(email, password));      
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Login); // maps your state to your component's props

In your reducers/auth.js:

const initialState = {
  authToken: '',
  isFetching: false,
  error: false,
  errorMessage: ''
}

export default function loginReducer(state=initialState, action) {
  switch (action.type) {
    case actionType.AUTH_REQUEST:
      return {
        ...state,
        isFetching: true
    };
    case actionType.AUTH_RECEIVE:
      return authLogin(state, action);
    case actionType.AUTH_ERROR:
      return {
        ...state,
        error: true,
        errorMessage: 'Incorrect login or password!'
      };
  }
  return state;
}

function authLogin(state, action) {
  const { authToken } = action;
  return {
    ...state,
    isFetching: false,
    authToken
  };
}

Then, in your code, you should be combining your reducer like:

import { combineReducers, createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import Login from './components/Login';
import login from './reducers/auth.js'; //import your default reducer function

//COMBINE REDUCERS
const reducers = combineReducers({
  //reducers go here 
  login, //name of your reducer => this is is why your access your state like state.login
});

//WRAP WITH STORE AND RENDER
const createStoreWithMiddleware = applyMiddleware()(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Login/>
  </Provider>
  , document.querySelector('.container'));

In Redux, you manage your state (setState) in a different layer (reducer) than your actual component. To do so, you need to map your state from the reducer to the component so you can use it as a props. This is why in your Login class, you are able to do :

const { errorMessage } = this.props; // now you can use errorMessage directly or this.props.errorMessage

This errorMessage comes from your state managed in your reducer and can be used in your component as this.props.errorMessage.

Here is the link to the tutorial which helped me understand Redux in React : https://github.com/happypoulp/redux-tutorial It should help you understand better the workflow

Comments