skip to Main Content

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


  1. 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 :

    import { connect } from 'react-redux';
    import MyComponent from '../components/MyComponent';
    import { updateFoo } from '../redux/actions';
    
    //Listen to the object 'foo' in the state. It is now accessible through 'this.props.foo' 
    const mapStateToProps = state => {
      return {
        foo: state.foo,
      };
    };
    
    //Map the actions to the props. Now accessible through 'this.props.updateFoo()'
    const mapDispatchToProps = dispatch => {
      return {
        updateFoo: foo => dispatch(updateFoo(foo)),
    
      };
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
    
    Login or Signup to reply.
  2. You should export the HOC returned by @connect()

    export default connect(
         null,
         { showRegisterDialog,
           closeDialog,
           redirectReplacePath,
           reloadPage
         }
    )
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search