skip to Main Content

I am building a application with Redux, React-Redux, and React. When I am doing connect with component and making action call, I receive an error:

Uncaught (in promise) TypeError: sendLoginRequest is not a function

My Code:

component/

import React, { useState } from "react";
import { connect } from 'react-redux';
import { sendLogin } from './Login.Action';
import { Google, Microsoft } from "@mui/icons-material";
import { NavLink, useNavigate } from "react-router-dom";
import * as constant from "../../constant/commonString";

const Login = ({
  userName,
  isError,
  loginSuccess,
  loginErrorMessage,
  triggerLogin
})=> {
  const navigate = useNavigate();
  const { sendRequest, loading } = useHttpClient();

  const [email, setLoginEmail] = useState("");
  const [password, setLoginPassword] = useState("");

  const handleEmail = (e) => {
    setLoginEmail(e.target.value);
  };

  const handlePassword = (e) => {
    setLoginPassword(e.target.value);
  };

  const handleLogin = (e) => {
    e.preventDefault();
    try {
     const log = triggerLogin({ email, password });
    } catch {
      console.log('errr');
    }
  };

  return (
    <div className="container">
      <div className="welcome">
        <h1>{constant.WELCOME}</h1>
      </div>
      <form>
        <input
          value={email}
          onChange={handleEmail}
          type="email"
          id="emailnote"
          name="email"
          required
          placeholder="Email address"
        />
        <input
          value={password}
          onChange={handlePassword}
          type="password"
          id="passwordnote"
          name="password"
          required
          placeholder="Password"
        />
        <Button
          type="submit"
          stylx="button_style button-text-design next-button"
          text=" Continue"
          onClick={handleLogin}
        />
      </form>
      <div className="details">
        <p>
          {constant.ACCOUNT_INFO}
          <NavLink className="signup" to="/signup">
            {constant.SIGNUP}
          </NavLink>
        </p>
      </div>
      <div className="login-options">
        <button className="google">
          <Google color="primary" />
          {constant.GOOGLE}
        </button>
        <button className="microsoft">
          <Microsoft color="primary" />
          {constant.MICROSOFT}
        </button>
      </div>
      <div className="terms">
        <NavLink to="/terms">{constant.TERMS}</NavLink>
        <span>|</span>
        <NavLink to="/policy">{constant.POLICY}</NavLink>
      </div>
    </div>
  );
};

const mapStateToProps = ({ login }) => ({ ...login });

export default connect(mapStateToProps, {
  triggerLogin: sendLogin,
})(Login);

Login.Action.js

/* eslint-disable */

import { createActions, handleActions, combineActions } from 'redux-actions';
import { post, } from '../../utils/api';
import {
  SEND_LOGIN_REQUEST,
  SEND_LOGIN_SUCCESS,
  SEND_LOGIN_FAILED,
  SET_TOKEN_ID
} from './Login.Action.constant';

const sendLoginRequest = createActions(SEND_LOGIN_REQUEST);
const sendLoginSuccess = createActions(SEND_LOGIN_SUCCESS);
const sendLoginFailed = createActions(SEND_LOGIN_FAILED);
export const setToken = createActions(SET_TOKEN_ID);

const API_URL = {
  LOGIN: 'users/login',
  LOGOUT: 'user/web/logout'
};

export const sendLogin = (data) => async (dispatch) => {
// here i am getting data
  dispatch(sendLoginRequest());
  const { error, response } = await post(`${API_URL.LOGIN}`, data);
  if (response) {
    dispatch(sendLoginSuccess(response));
  } else if (error) {
    dispatch(sendLoginFailed(error));
  }
};

Error

Login.Action.js:44 Uncaught (in promise) TypeError: sendLoginRequest is not a function
    at Login.Action.js:44:1
    at Object.dispatch (redux-thunk.mjs:5:1)
    at dispatch (<anonymous>:6:7384)
    at boundActionCreators.<computed> (bindActionCreators.ts:12:1)
    at handleLogin (Login.js:37:1)

issue img

I am unable to find whats wrong is going here. I have tried React-Action creation many solution related to this, but unable to solve this problem.

2

Answers


  1. Looking at the docs for redux-actions, createActions takes an object and also returns an object, not a function. Did you mean to call createAction instead?

    Login or Signup to reply.
  2. The basic issue is that you have used createActions (plural, with an "s") instead of createAction to create your individual actions. It appears that createActions handles this as an edge-case though, and returns an object of action creator functions.

    export default function createActions(actionMap, ...identityActions) {
      const options = isPlainObject(getLastElement(identityActions))
        ? identityActions.pop()
        : {};
      invariant(
        identityActions.every(isString) &&
          (isString(actionMap) || isPlainObject(actionMap)),
        'Expected optional object followed by string action types'
      );
      if (isString(actionMap)) {                  // <-- 
        return actionCreatorsFromIdentityActions( // <--
          [actionMap, ...identityActions],        // <--
          options                                 // <--
        );                                        // <--
      }
      return {
        ...actionCreatorsFromActionMap(actionMap, options),
        ...actionCreatorsFromIdentityActions(identityActions, options)
      };
    }
    

    So (safely) assuming that SEND_LOGIN_REQUEST is a string value, i.e. "SEND_LOGIN_REQUEST", the following line

    const sendLoginRequest = createActions(SEND_LOGIN_REQUEST);
    

    produces an object that contains the action creator functions instead of the action creator function directly.

    {
      sendLoginRequest: (...args) => {....},
    }
    

    versus

    (...args) => {....}
    

    If you wanted to dispatch this sendLoginRequest action then it’d likely look like dispatch(sendLoginRequest.sendLoginRequest()); where you access the sendLoginRequest of the returned action map object (also named sendLoginRequest).

    You should probably just use the createAction utility instead to keep it simple (and very likely what you meant to do from the start).

    import { createAction, handleActions, combineActions } from 'redux-actions';
    import { post } from '../../utils/api';
    import {
      SEND_LOGIN_REQUEST,
      SEND_LOGIN_SUCCESS,
      SEND_LOGIN_FAILED,
      SET_TOKEN_ID
    } from './Login.Action.constant';
    
    const sendLoginRequest = createAction(SEND_LOGIN_REQUEST);
    const sendLoginSuccess = createAction(SEND_LOGIN_SUCCESS);
    const sendLoginFailed = createAction(SEND_LOGIN_FAILED);
    export const setToken = createAction(SET_TOKEN_ID);
    
    ...
    
    dispatch(sendLoginRequest()); // <-- now properly a function
    

    Recommendation: Use Redux-Toolkit

    You are maintaining/implementing a very outdated form of Redux. You should really integrate Redux-Toolkit which cuts out much of the Redux boilerplate code (e.g. declaring actions types, creating action/success/failure actions, etc…). If you are already familiar with Redux this is about a 5-10 minute upgrade to swap out the store creation.

    Login.Action.js could be re-written as the following:

    import { createAsyncThunk } from '@reduxjs/toolkit';
    import { post } from '../../utils/api';
    
    const API_URL = {
      LOGIN: 'users/login',
      LOGOUT: 'user/web/logout'
    };
    
    export const sendLogin = createAsyncThunk(
      "auth/sendLogin",
      async (data, thunkApi) => {
        try {
          const { error, response } = await post(API_URL.LOGIN, data);
          if (response) {
            return response;
          } else if (error) {
            return thunkApi.rejectWithValue(error);
          }
        } catch(error) {
          return thunkApi.rejectWithValue(error);
        }
      },
    );
    

    If post simply throws errors/rejections instead of resolving with an error property though the code becomes quite trivial:

    export const sendLogin = createAsyncThunk(
      "auth/sendLogin",
      (data, thunkApi) => {
        try {
          return post(API_URL.LOGIN, data);
        } catch(error) {
          return thunkApi.rejectWithValue(error);
        }
      },
    );
    

    createAsyncThunk generates three actions for you:

    New Replaces
    sendLogin.pending sendLoginRequest
    sendLogin.fulfilled sendLoginSuccess
    sendLogin.rejected sendLoginFailed

    You can reference these new actions in any existing case reducers:

    const someReducer = (state, action) => {
      switch(action.type) {
        ...
    
        case sendLogin.pending.type:
          // update any pending action state
    
        case sendLogin.fulfilled.type:
          // update any success action state
          // action.payload is the resolved response value
    
        case sendLogin.rejected.type:
          // update any failure action state
          // action.payload is the rejected error value
    
        ...
    
        default:
          return state;
      }
    };
    

    Additional Suggestion

    Using the connect Higher Order Component is also considered a dated practice these days. Use the useDispatch and useSelector hooks to access the dispatch function and subscribe to state updates.

    Example:

    import React, { useState } from "react";
    import { useDispatch, useSelector } from 'react-redux';
    import { sendLogin } from './Login.Action';
    import { Google, Microsoft } from "@mui/icons-material";
    import { NavLink, useNavigate } from "react-router-dom";
    import * as constant from "../../constant/commonString";
    
    const Login = ()=> {
      const dispatch = useDispatch();
      const {
        userName,
        isError,
        loginSuccess,
        loginErrorMessage,
      } = useSelector(state => state.login);
    
      const navigate = useNavigate();
    
      const { sendRequest, loading } = useHttpClient();
    
      const [email, setLoginEmail] = useState("");
      const [password, setLoginPassword] = useState("");
    
      const handleEmail = (e) => {
        setLoginEmail(e.target.value);
      };
    
      const handlePassword = (e) => {
        setLoginPassword(e.target.value);
      };
    
      const handleLogin = async (e) => {
        e.preventDefault();
        try {
          const log = await dispatch(sendLogin({ email, password }));
          // or if using RTK createAsyncThunk unwrap the result
          // const log = await dispatch(sendLogin({ email, password })).unwrap();
        } catch {
          console.log('errr');
        }
      };
    
      return (
        <div className="container">
          <div className="welcome">
            <h1>{constant.WELCOME}</h1>
          </div>
          <form onSubmit={handleLogin}>
            <input
              value={email}
              onChange={handleEmail}
              type="email"
              id="emailnote"
              name="email"
              required
              placeholder="Email address"
            />
            <input
              value={password}
              onChange={handlePassword}
              type="password"
              id="passwordnote"
              name="password"
              required
              placeholder="Password"
            />
            <Button
              type="submit"
              stylx="button_style button-text-design next-button"
              text=" Continue"
            />
          </form>
          <div className="details">
            <p>
              {constant.ACCOUNT_INFO}
              <NavLink className="signup" to="/signup">
                {constant.SIGNUP}
              </NavLink>
            </p>
          </div>
          <div className="login-options">
            <button className="google">
              <Google color="primary" />
              {constant.GOOGLE}
            </button>
            <button className="microsoft">
              <Microsoft color="primary" />
              {constant.MICROSOFT}
            </button>
          </div>
          <div className="terms">
            <NavLink to="/terms">{constant.TERMS}</NavLink>
            <span>|</span>
            <NavLink to="/policy">{constant.POLICY}</NavLink>
          </div>
        </div>
      );
    };
    
    export default Login;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search