Search code examples
reactjsreduxreact-reduxredux-thunk

UseEffect not fired when redux props change caused by async api call with redux-thunk


I have a functional login page connected with redux, I'm firing an async event onSubmit that will trigger the emailLogin action, I am using useEffect to detect the change of the isLoading prop to see whether login finished or not. If login success, the redux store should have the user object, if failed, the user should remain null.

The question is, I know that the login is success, which should triggered the change of isLoading, the parameter that decide whether the useEffect, however, the useEffect is not fired. Also, the console.log('done'); after the line await emailLogin(authData); is never fired. Ssomething is wrong.

import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { emailLogin } from '../actions/index';

function Login({ user, isLoading, emailLogin }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const history = useHistory();

  useEffect(() => {
    console.log('useEffect fired', user, isLoading); //<-----This does not fire after login success
    if (user) {
      history.push('/protected_home');
    } 
  }, [isLoading]);

  const submitEmailLoginForm = async (e) => {
    e.preventDefault();
    const authData = { email, password };
    await emailLogin(authData);
    console.log('done'); // <------- This is never fired
  };

  return (
    <div>
      <h2>Login</h2>
      <Link to="/">back</Link>
      <form onSubmit={submitEmailLoginForm}>
        <label>
          email:
          <input
            type="text"
            name="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </label>
        <label>
          password:
          <input
            type="text"
            name="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </label>
        <input type="submit" value="Submit" />
      </form>
    </div>
  );
}

const mapStateToProps = (state) => ({
  user: state.user,
  isLoading: state.isLoading
});

const mapDispatch = {
  emailLogin: emailLogin
};

export default connect(mapStateToProps, mapDispatch)(Login);

My action file:

import axios from 'axios';

export const authActions = {
  EMAIL_LOGIN_START: '@@EMAIL_LOGIN_START',
  EMAIL_LOGIN_SUCCESS: '@@EMAIL_LOGIN_SUCCESS'
};

export const emailLogin = ({ email, password }) => async (dispatch) => {
  dispatch({ type: authActions.EMAIL_LOGIN_START });
  try {
    const response = await axios.post('http://localhost:5001/api/auth', {
      email: email,
      password: password
    });
    dispatch({
      type: authActions.EMAIL_LOGIN_SUCCESS,
      payload: {
        user: { ...response.data }
      }
    });
  } catch (error) {
    console.log('Should dispatch api error', error.response);
  }
};

My Reducer:

import { authActions } from '../actions/index';

const initialState = {
  user: null,
  isLoading: false
};

const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case authActions.EMAIL_LOGIN_START:
      return { ...state, isLoading: true };
    case authActions.EMAIL_LOGIN_SUCCESS:
      console.log('Reducer check => Login is success'); //<-----this line is printed
      return { ...state, user: action.payload.user, isLoading: false };
    default:
      return state;
  }
};

export default userReducer;

In the reducer, I see that the success action is actually triggered by checking the console.log(). Also in the redux dev tool, I can actually see that the login is success and the isLoading prop has changed : enter image description here


Solution

  • This solve my problem

    const mapStateToProps = (state) => ({
      user: state.userReducer.user,
      isLoading: state.userReducer.isLoading
    });