I am very new to ReactJS and redux.
I am trying to build a new application based on the following reference.
https://github.com/shalomeir/snippod-starter-demo-app-front
Now, I am trying to integrate the login section with AWS Cognito. Everything is fine when I used the traditional way to build the login flow, but when I merged it to redux, I am not able to update the state. May I know if I missed anything?
containers/DialogWindows/LoginDialog.js
import React, { Component, PropTypes } from 'react';
import Radium from 'radium';
import _ from 'lodash';
import $ from 'jquery';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { reduxForm } from 'redux-form';
import { defineMessages, FormattedMessage } from 'react-intl';
import { showLoginDialog, showRegisterDialog,
closeDialog, redirectReplacePath, reloadPage } from 'ducks/application/application';
import { loginSuccess, login } from 'ducks/authentication/auth';
import { facebook, aws } from 'constants/config';
import FacebookLogin from './FacebookLogin';
import TwitterLogin from './TwitterLogin';
import { Link } from 'react-router';
//Do not connect this action
import { switchLangAndDeleteLanguageQuery } from 'ducks/application/application';
import { showDelayedToastMessage } from 'ducks/messages/toastMessage';
import toastMessages from 'i18nDefault/toastMessages';
import loginValidation from './loginValidation';
import { CognitoUserPool, CognitoUserAttribute, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
const styles = require('./DialogStyles');
@connect(
null,
{ showRegisterDialog, closeDialog, redirectReplacePath, reloadPage }
)
@reduxForm({
form: 'login',
fields: ['emailId', 'password'],
validate: loginValidation
})
@Radium
export default class LoginDialog extends Component {
static propTypes = {
//auth: PropTypes.object.isRequired,
redirectReplacePath: PropTypes.func.isRequired,
showRegisterDialog: PropTypes.func.isRequired,
closeDialog: PropTypes.func.isRequired,
reloadPage: PropTypes.func.isRequired,
fields: PropTypes.object.isRequired,
error: PropTypes.string,
errors: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
initializeForm: PropTypes.func.isRequired,
invalid: PropTypes.bool.isRequired,
dirty: PropTypes.bool.isRequired,
submitting: PropTypes.bool.isRequired,
values: PropTypes.object.isRequired
};
constructor() {
super();
this.state = { changed: false };
this._onSubmit = this._onSubmit.bind(this);
}
componentWillReceiveProps(nextProps) {
if (!_.isEqual(this.props.values, nextProps.values) && !this.state.changed && nextProps.dirty) {
this.setState({ changed: true });
}
}
componentWillMount(dispatch) {
//Use case 16
var userPool = new CognitoUserPool(aws.cognito);
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, session) {
if (err) {
alert(err);
return;
}
console.log('session validity: ' + session.isValid());
//dispatch(loginSuccess());
const credentialsPath = 'cognito-idp.' + aws.cognito.region + '.amazonaws.com/' + aws.cognito.UserPoolId;
// NOTE: getSession must be called to authenticate user before calling getUserAttributes
cognitoUser.getUserAttributes(function(err, attributes) {
if (err) {
// Handle error
} else {
// Do something with attributes
}
});
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId : aws.cognito.IdentityPoolId,
Logins : {
// Change the key below according to the specific region your user pool is in.
credentialsPath : session.getIdToken().getJwtToken()
}
});
// Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
// return function(dispatch){
// dispatch(loginSuccess());
// };
return (dispatch, getState) => {
//console.log(getState().application.lang);
return dispatch({
types: LOGIN_SUCCESS,
});
};
});
}
}
_onSubmit(values, dispatch) {
this.props.initializeForm();
return new Promise((resolve, reject) => {
dispatch(
login(values)
).then((response) => {
//const account = response.entities.accounts[response.result];
this.props.reloadPage();
//dispatch(switchLangAndDeleteLanguageQuery(account.language.split('-')[0]));
// dispatch(showDelayedToastMessage({
// type: 'info',
// title: toastMessages.loginTitle,
// body: Object.assign(toastMessages.loginBody, { values: { username: account.username } })
// }, 300));
this.props.redirectReplacePath();
resolve(response);
}).catch((error) => {
reject({ _error: error.message });
});
});
}
render() {
const { error, errors, fields: { emailId, password }, handleSubmit, invalid,
submitting } = this.props;
const { changed } = this.state;
return (
<div className="login ui text container main-container">
<img src="/images/logo.png" className="ui centered image" />
<form className={classNames('ui form login-form one column stackable center aligned page grid', { 'error': (invalid && changed) })} onSubmit={handleSubmit(this._onSubmit)}>
<div className="ui grid inner">
<div className="ui segment attached top">SIGN IN</div>
<div className="ui segment attached social-login">
<FacebookLogin />
<TwitterLogin />
</div>
<div className="ui attached segment cognito-login">
<div className={classNames('field', { 'error': (emailId.invalid && changed) })}>
<label>EMAIL ADDRESS <span className="red">*</span></label>
<div className="ui left icon email input">
{/*<i className="user icon" />*/}
<input type="email" name="emailId" placeholder="Your Email" ref="emailId" {...emailId} />
</div>
<div className="ui email pointing red basic small label transition hidden" style={styles.errorText}>
{errors.emailId ? <FormattedMessage {...errors.emailId} /> : null}
</div>
</div>
<div className={classNames('field', { 'error': (password.invalid && changed) })}>
<div className="ui grid float">
<div className="two column row field">
<label className="left floated column">YOUR PASSWORD <span className="red">*</span></label>
<Link to="/forgetpassword" className="right floated column">Forgotten password?</Link>
</div>
</div>
<div className="ui left icon password input">
{/*<i className="lock icon" />*/}
<input type="password" name="password" placeholder="Password" ref="password" {...password} />
</div>
<div className="ui password pointing red basic small label transition hidden" style={styles.errorText}>
{errors.password ? <FormattedMessage {...errors.password} /> : null}
</div>
</div>
<button type="submit" className={classNames('ui fluid large blue button', { 'loading': submitting })}
disabled={submitting || invalid} >
{/*<FormattedMessage {...i18n.button} />*/}
SIGN IN
</button>
<div className="field">
<div className="ui checkbox">
<input type="checkbox" tabIndex="0" className="hidden" name="checkbox1" id="checkbox1" />
<label htmlFor="checkbox1">Remember Me</label>
</div>
</div>
</div>
<div id="login-general-error-message" className="ui general error message hidden" style={styles.errorText}>
{error}
</div>
</div>
</form>
</div>
);
}
}
ducks/authentication/auth.js
const debug = require('utils/getDebugger')('auth');
import { switchLangAndDeleteLanguageQuery, reloadPage, pushPath } from 'ducks/application/application';
import { showDelayedToastMessage } from 'ducks/messages/toastMessage';
import toastMessages from 'i18nDefault/toastMessages';
import Schemas from 'ducks/Schemas';
import { facebook, aws } from 'constants/config';
//import { AWS } from 'aws-sdk';
import { CognitoUserPool, CognitoUserAttribute, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
const LOAD = 'authentication/auth/LOAD';
const LOAD_SUCCESS = 'authentication/auth/LOAD_SUCCESS';
const LOAD_FAIL = 'authentication/auth/LOAD_FAIL';
const LOGIN = 'authentication/auth/LOGIN';
const LOGIN_SUCCESS = 'authentication/auth/LOGIN_SUCCESS';
const LOGIN_FAIL = 'authentication/auth/LOGIN_FAIL';
const initialState = {
loggedIn: false,
loaded: false,
account: null,
error: null
};
export default function reducer(state = initialState, action = {}) {
const { INIT_ALL_STATE } = require('ducks/globalActions');
switch (action.type) {
case LOAD:
return state;
case LOAD_SUCCESS:
if (action.response) {
return {
...state,
loggedIn: true,
loaded: true,
account: action.response.entities.accounts[action.response.result],
};
}
return {
...state,
loggedIn: false,
loaded: true,
error: null
};
case LOAD_FAIL:
return {
...state,
loading: false,
loaded: true,
error: action.error
};
case LOGIN:
return state;
case LOGIN_SUCCESS:
return {
...state,
loggedIn: true,
account: action.response.entities.accounts[action.response.result]
};
case LOGIN_FAIL:
return {
...state,
loggedIn: false,
account: null,
error: action.error
};
case INIT_ALL_STATE:
return initialState;
default:
return state;
}
}
export function loginSuccess() {
return { type: LOGIN_SUCCESS };
}
export function isLoaded(globalState) {
return globalState.auth && globalState.auth.loaded;
}
export function load() {
return {
types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
promise: (client) => client.get('/auth/load_auth/', {
schema: Schemas.MY_ACCOUNT
})
};
}
export function login(loginForm) {
// return (dispatch, getState) => {
// return dispatch({
// types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL],
// promise: (client) => client.post('/auth/login/', {
// data: {
// email: loginForm.emailId,
// password: loginForm.password
// },
// params: {
// language: getState().application.lang
// },
// schema: Schemas.MY_ACCOUNT
// })
// });
// };
//Use case 4, 23
var authenticationData = {
Username : loginForm.emailId,
Password : loginForm.password,
};
var authenticationDetails = new AuthenticationDetails(authenticationData);
var userPool = new CognitoUserPool(aws.cognito);
var userData = {
Username : loginForm.emailId,
Pool : userPool
};
var cognitoUser = new CognitoUser(userData);
console.log(authenticationDetails);
console.log("Username: " + authenticationData.Username + " Password: " + authenticationData.Password );
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
/*Use the idToken for Logins Map when Federating User Pools with Cognito Identity or when passing through an Authorization Header to an API Gateway Authorizer*/
console.log('idToken + ' + result.idToken.jwtToken);
const credentialsPath = 'cognito-idp.' + aws.cognito.region + '.amazonaws.com/' + aws.cognito.UserPoolId;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId : aws.cognito.IdentityPoolId, // your identity pool id here
Logins : {
// Change the key below according to the specific region your user pool is in.
credentialsPath : result.getIdToken().getJwtToken()
}
});
return { type: LOGIN_SUCCESS }
//return (dispatch, getState) => {
// return dispatch({
// types: LOGIN_SUCCESS,
// });
//};
},
onFailure: function(err) {
alert(err);
return { type: LOGIN_SUCCESS }
},
newPasswordRequired: function(userAttributes, requiredAttributes) {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// the api doesn't accept this field back
delete userAttributes.email_verified;
// Get these details and call
cognitoUser.completeNewPasswordChallenge(loginForm.password, userAttributes, this);
}
});
}
// thunk action that dispatch login action and then dispatch follow action such as switch lang.
// TODO: Check return response or error. This is not use. Instead, login process is handled in react login dialog.
export function loginAndFollow(loginForm) {
return (dispatch, getState) => {
dispatch(
login(loginForm)
).then((response) => {
// const account = response.entities.accounts[response.result];
// dispatch(switchLangAndDeleteLanguageQuery(account.language.split('-')[0]));
// dispatch(showDelayedToastMessage({
// type: 'info',
// title: toastMessages.loginTitle,
// body: Object.assign(toastMessages.loginBody, { values: { username: account.username } })
// }, 500));
return response;
}).catch((error) => {
debug('Error occurred : ', error);
return error;
});
};
return dispatch(login(loginForm));
}
containers/Ground/Ground.js
import React, { Component, PropTypes } from 'react';
import Radium from 'radium';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { defineMessages, FormattedMessage } from 'react-intl';
import { showLoginDialog, showRegisterDialog, redirectReplacePath } from 'ducks/application/application';
import {
LoginDialog,
RegisterDialog
} from 'containers';
import { DialogWindow } from 'layout/DialogWindow/DialogWindow';
@connect(
createSelector([
state => state.auth,
state => state.application
], (auth, application) => {
return { auth, application };
}),
{ showLoginDialog, showRegisterDialog, redirectReplacePath, DialogWindow }
)
@Radium
export default class Ground extends Component {
static propTypes = {
location: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
application: PropTypes.object.isRequired,
showLoginDialog: PropTypes.func.isRequired,
showRegisterDialog: PropTypes.func.isRequired,
redirectReplacePath: PropTypes.func.isRequired
};
constructor() {
super();
this.checkAuth = this.checkAuth.bind(this);
}
componentWillMount() {
const redirect = this.checkAuth();
if (!redirect) {
if (this.props.location.pathname === '/login') {
this.props.showLoginDialog();
this.setState({ page: 'login' });
}
if (this.props.location.pathname === '/register') {
this.props.showRegisterDialog();
this.setState({ page: 'register' });
}
}
}
componentWillReceiveProps(nextProps) {
if (!this.props.auth.loggedIn && nextProps.auth.loggedIn) {
this.props.redirectReplacePath('/');
}
}
checkAuth() {
//console.log('hello will login check auth');
if (this.props.auth.loggedIn) {
// You already logged in, so do not needed to be here!
this.props.redirectReplacePath('/');
return true;
}
return false;
}
render() {
const messageHeader = this.state.page === 'login' ? i18n.loginMessageHeader : i18n.registerMessageHeader;
const messageBody = this.state.page === 'login' ? i18n.loginMessageBody : i18n.registerMessageBody;
const { auth, application } = this.props;
let content = null;
// content = <DialogWindow auth={this.props.auth} application={this.props.application} />;
if (this.state.page === 'login') {
content = <LoginDialog />;
} else if (this.state.page === 'register') {
content = <RegisterDialog />;
}
return (
<div className="loading ui text container main-container">
{content}
{/*<Helmet title={this.state.page === 'login' ? 'Login' : 'Register'} />
<div className="ui message">
<div className="header">
<FormattedMessage {...messageHeader} />
</div>
<p><FormattedMessage {...messageBody} /></p>
</div>*/}
</div>
);
}
}
2
Answers
To listen to the state and use dispatch, you have to map the state and dispatch to your component’s props.
To do this, I use ah higher order component :
You should export the HOC returned by
@connect()