2
votes

I'm currently working on an e-commerce project and we are using react-redux. But I'm having a problem with returning a response from API instances using Axios request call on the action for Login with Jwt module.

This is Axios file that consists of base URLs and instances.

api/lib/axios.js:

/* eslint-disable no-nested-ternary */
import axios from "axios"
import { store } from "../../redux/store"
import { REFRESH_TOKEN } from "../../redux/auth/constant"
import Environment from "./environment"

const axiosInstance = axios.create({
  baseURL: Environment.BASE_URL,
  headers: {
    Authorization: localStorage.getItem("access")
      ? `JWT${localStorage.getItem("access")}`
      : "none",
    "Content-Type": "application/json",
    accept: "application/json"
  }
})

axiosInstance.interceptors.response.use(
  (response) => response,
  (error) => {
    const originalRequest = error.config

    if (
      error.response.status === 401 &&
      error.response.statusText === "Unauthorized"
    ) {
      const body = store.getState().auth.token.refresh
      return axiosInstance
        .post("api/token/refresh/", { refresh: body })
        .then((response) => {
          store.dispatch({
            type: REFRESH_TOKEN,
            payload: response.data
          })
          axiosInstance.defaults.headers.Authorization = `JWT ${response.data.access}`
          originalRequest.headers.Authorization = `JWT ${response.data.access}`

          return axiosInstance(originalRequest)
        })
    }
    return Promise.reject(error)
  }
)

export default axiosInstance

Here is also an environment file where all our environment variables are stored.

api/lib/environment.js:

const Environment = {
  BASE_URL: "http://babylandworld.com:8000/"
}

export default Environment

I'm calling an API request from auth file inside api/lib/auth as:

api/lib/auth.js:

/* eslint-disable import/prefer-default-export */
import axiosInstance from "./axios"
// Auth Request from backend api using axios
const auth = async (username, password) => {
  const body = { username, password }
  const response = await axiosInstance
    .post(`/api/token/obtain/`, body)
    .then((res) => {
      axiosInstance.defaults.headers.Authorization = `JWT ${res.data.access}`
    })
  return response
}
export { auth }

I have dispatch the response from the api/auth.js file to auth/action.js. But I'm getting an error on dispatch and catch with no user data after submitting the login form.

Here is action.js code: auth/action.js:

import * as constants from "./constant"

import { auth } from "../../api/lib/auth"

const loginBegin = () => ({
  type: constants.LOGIN_BEGIN
})
const login = ({ username, password }) => (dispatch) => {
  dispatch({
    type: constants.LOGIN_SUCCESS,
    paylaod: auth(username, password).data
  }).catch(() =>
    dispatch({
      type: constants.LOGIN_FAIL
    })
  )
}

export { loginBegin, login }

Also, I have created actions types in constant.js file as:

auth/constant.js:

export const REFRESH_TOKEN = "REFRESH_TOKEN"
export const LOGIN_BEGIN = "LOGIN_BEGIN"
export const LOGIN_SUCCESS = "LOGIN_SUCCESS"
export const LOGIN_FAIL = "LOGIN_FAIL"
export const AUTH_ERROR = "AUTH_ERROR"
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS"

Here is reducer file as: auth/reducer.js:

import { actions } from "react-table"
import * as constants from "./constant"

const initialState = {
  token: localStorage.getItem("token"),
  isAuthenticated: null,
  isLoading: false,
  user: null
}
function authReducer(state = initialState, action) {
  switch (action.type) {
    case constants.LOGIN_BEGIN:
      return {
        ...state,
        isAuthenticated: false,
        isLoading: true,
        user: ""
      }
    case constants.LOGIN_SUCCESS:
      return {
        ...state,
        ...action.payload,
        isAuthenticated: true,
        isLoading: false
      }
    case constants.LOGIN_FAIL:
    case constants.LOGOUT_SUCCESS:
      return {
        ...state,
        token: null,
        isAuthenticated: false,
        user: null,
        isLoading: false
      }
    default:
      return state
  }
}
export default authReducer

This is our login page component as: pages/auth/loginTabset.js:

/* eslint-disable jsx-a11y/anchor-has-content */
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { Component } from "react"

import PropTypes from "prop-types"
import { connect } from "react-redux"
import { Redirect } from "react-router-dom"
import { login } from "../../redux/auth/action"

export class LoginTabset extends Component {
  state = {
    username: "",
    password: ""
  }

  clickActive = (event) => {
    document.querySelector(".nav-link").classList.remove("show")
    event.target.classList.add("show")
  }

  handleSubmit = (e) => {
    e.preventDefault()
    this.props.login(this.state.username, this.state.password)
  }

  handleChange = (e) => this.setState({ [e.target.name]: e.target.value })

  render() {
    if (this.props.isAuthenticated) {
      return <Redirect to="/dashboard" />
    }
    const { username, password } = this.state
    return (
      <div>
        <h4 className="text-center">Login Panel</h4>
        <hr />
        <form
          className="form-horizontal auth-form"
          onSubmit={this.handleSubmit}>
          <div className="form-group">
            <input
              required=""
              name="username"
              value={username}
              type="text"
              className="form-control"
              placeholder="Username"
              id="username"
              onChange={this.handleChange}
            />
          </div>
          <div className="form-group">
            <input
              required=""
              name="password"
              value={password}
              type="password"
              className="form-control"
              placeholder="Password"
              id="password"
              onChange={this.handleChange}
            />
          </div>
          <div className="form-terms">
            <div className="custom-control custom-checkbox mr-sm-2">
              <input
                type="checkbox"
                className="custom-control-input"
                id="customControlAutosizing"
              />
              <label className="d-block" htmlFor="chk-ani2">
                <input
                  className="checkbox_animated"
                  id="chk-ani2"
                  type="checkbox"
                />
                Reminder Me{" "}
                <span className="pull-right">
                  {" "}
                  <a href="#" className="btn btn-default forgot-pass p-0">
                    lost your password
                  </a>
                </span>
              </label>
            </div>
          </div>
          <div className="form-button">
            <button className="btn btn-primary" type="submit">
              Login
            </button>
          </div>
          <div className="form-footer">
            <span>Or Login up with social platforms</span>
            <ul className="social">
              <li>
                <a className="fa fa-facebook" href="" />
              </li>
              <li>
                <a className="fa fa-twitter" href="" />
              </li>
              <li>
                <a className="fa fa-instagram" href="" />
              </li>
              <li>
                <a className="fa fa-pinterest" href="" />
              </li>
            </ul>
          </div>
        </form>
      </div>
    )
  }
}
LoginTabset.propTypes = {
  login: PropTypes.func.isRequired,
  isAuthenticated: PropTypes.bool.isRequired
}

const mapStateToProps = (state) => ({
  isAuthenticated: state.auth.isAuthenticated
})
export default connect(mapStateToProps, { login })(LoginTabset)

Problem: when submitting the login form, there is an error on dispatch an action from api/lib/auth and don't get any response data from the API server.

Please, anyone, help to solve this problem as soon as possible.

1

1 Answers

1
votes

dispatch function doesn't return you a promise to use. You must instead call the auth function and wait on it before firing dispatch since it an async function.

You can use async-await with try catch like below

const login = ({ username, password }) =>async (dispatch) => {

  try {
     const res = await auth(username, password);
     dispatch({
       type: constants.LOGIN_SUCCESS,
       paylaod: res.data,
     })
  } catch(error ) {
    dispatch({
      type: constants.LOGIN_FAIL
    });
  }
}

export { loginBegin, login }