6
votes

I have been working on authentication with my project. I have a REST api backend that serves JWT tokens. My front end stack is ReactJS, Redux, Axios and Redux Thunk.

My question is why when I submit my form it does not send any credentials?

But when I console log the action and payload on credChange it seems to be correct. Am I not setting the state somewhere? Also, axios does not catch the 400 Bad Request error.

Here is my code:

AuthActions.js

export const credChange = ({ prop, value }) => {
  return {
    type: CRED_CHANGE,
    payload: { prop, value },
  };
};
export const logoutUser = () => {
  return (dispatch) => {
    dispatch({ type: LOGOUT_USER });
  };
};
const loginSuccess = (dispatch, response) => {
  dispatch({
    type: LOGIN_USER_SUCCESS,
    payload: response.data.token,
  });
};
const loginError = (dispatch, error) => {
  dispatch({
    type: LOGIN_USER_ERROR,
    payload: error.response.data,
  });
};
export const loginUser = ({ empNum, password }) => {
  return (dispatch) => {
    dispatch({ type: LOGIN_USER });
    axios({
      method: 'post',
      url: 'http://127.0.0.1:8000/profiles_api/jwt/authTK/',
      data: {
        emp_number: empNum,
        password,
      },
    })
      .then(response => loginSuccess(dispatch, response))
      .catch(error => loginError(dispatch, error));
  };
};

AuthReducer.js

const INITIAL_STATE = {
  empNum: '',
  password: '',
  empNumErr: null,
  passwordErr: null,
  authTK: null,
  loading: false,
};

export default (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case CRED_CHANGE:
      return { ...state, [action.payload.prop]: action.payload.value };
    case LOGIN_USER:
      return {
        ...state,
        ...INITIAL_STATE,
        loading: true,
      };
    case LOGOUT_USER:
      return {
        ...state,
        INITIAL_STATE,
      };
    case LOGIN_USER_SUCCESS:
      return {
        ...state,
        ...INITIAL_STATE,
        authTK: action.payload,
      };
    case LOGIN_USER_ERROR:
      return {
        ...state,
        ...INITIAL_STATE,
        empNumErr: action.payload.emp_number,
        passwordErr: action.payload.password,
      };
    default:
      return state;
  }
};

LoginForm.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

import {
  credChange,
  loginUser,
  logoutUser,
} from '../Actions';

class LoginForm extends Component {
  constructor() {
    super();
    this.onFormSubmit = this.onFormSubmit.bind(this);
    this.renderEmpNumErr = this.renderEmpNumErr.bind(this);
    this.empNumChange = this.empNumChange.bind(this);
    this.passwordChange = this.passwordChange.bind(this);
  }
  onFormSubmit() {
    const { empNum, password } = this.props;
    this.props.loginUser({ empNum, password });
  }
  empNumChange(text) {
    this.props.credChange({ prop: 'empNum', value: text.target.value });
  }
  passwordChange(text) {
    this.props.credChange({ prop: 'password', value: text.target.value });
  }
  renderEmpNumErr() {
    if (this.props.empNumErr) {
      return (
        <p>
          {this.props.empNumErr}
        </p>
      );
    }
    return null;
  }
  render() {
    return (
      <div>
        <form onSubmit={this.onFormSubmit}>
          <label htmlFor="numberLabel">Employee Number</label>
          <input
            id="numberLabel"
            type="password"
            value={this.props.empNum}
            onChange={this.empNumChange}
          />
          <label htmlFor="passLabel">Password</label>
          <input
            id="passLabel"
            type="password"
            value={this.props.password}
            onChange={this.passwordChange}
          />
          <button type="submit">Login</button>
        </form>
        {this.renderEmpNumErr()}
      </div>
    );
  }
}

const mapStateToProps = ({ counter }) => {
  const {
    empNum,
    password,
    loading,
    empNumErr,
    passwordErr,
    authTK,
  } = counter;
  return {
    empNum,
    password,
    loading,
    empNumErr,
    passwordErr,
    authTK,
  };
};

export default connect(mapStateToProps, { credChange, loginUser, logoutUser })(LoginForm);

After Submitting form with credentials

The console says:

POST XHR http://127.0.0.1:8000/profiles_api/jwt/authTK/ [HTTP/1.0 400 Bad Request 5ms]

And the POST request Raw Data is blank, therefore no credentials were sent.

{"emp_number":["This field is required."],"password":["This field is required."]}

EDIT If there is any other information I can provide please say so but I think this should be sufficient.

1

1 Answers

2
votes

Looks like empNum and password aren't getting set in the state. This is because the action object returned by credChange doesn't get dispatched, so the reducer never get called:

// dispatch calls the reducer which updates the state
dispatch(actionCreator())

// returns an action object, doesn't call reducer
actionCreator() 

You can dispatch actions automatically by calling a bound action creator:

// calls the reducer, updates the state
const boundActionCreator = () => {dispatch(actionCreator())}

// call boundActionCreator in your component
boundActionCreator()

mapDispatchToProps can be used to define bound action creators (to be passed as props):

const mapDispatchToProps = (dispatch) => {

  return {
      credChange: ({ prop, value }) => {dispatch(credChange({prop, value})},
      loginUser: ({ empNum, password }) => {dispatch(loginUser({empNum, password})},
      logoutUser: () => {dispatch(logoutUser()},
  }
}

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

This should solve the state update issue, allowing props that read from state (empNumber, password, etc.) to update as well.