Om3ga
Om3ga

Reputation: 32823

useEffect hook is turning into infinite loop even when the dependency is not changing all the time

Below is my component in reactjs.

import React, { useState, useEffect } from 'react';
import { Link, Redirect } from 'react-router-dom';
import { connect, useDispatch, useSelector } from 'react-redux';

import { loginUserAction } from '../actions/authenticationActions';
import { setCookie } from '../utils/cookies';

const LoginPage = () => {
  const [isSuccess, setSuccess] = useState(false);
  const [message, setMessage] = useState('');
  const dispatch = useDispatch();
  const login = useSelector(state => state.login.response);

  console.log(login);

  useEffect(() => {
    if (login !== undefined) {
      setSuccess(login.success);
      setMessage(login.message);

      if (isSuccess) {
        setCookie('token', login.token, 1);
      }
    }
  }, [login]);

  const onHandleLogin = (event) => {
    event.preventDefault();

    const email = event.target.email.value;
    const password = event.target.password.value;

    dispatch(loginUserAction({
      email, password,
    }));
  }

  return (
    <div>
      <h3>Login Page</h3>
      {!isSuccess ? <div>{message}</div> : <Redirect to='dashboard' />}
      <form onSubmit={onHandleLogin}>
        <div>
          <label htmlFor="email">Email</label>
          <input type="email" name="email" id="email" />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input type="password" name="password" id="password" />
        </div>
        <div>
          <button>Login</button>
        </div>
      </form>
      Don't have account? <Link to='register'>Register here</Link>
    </div>
  );
};

export default LoginPage;

It logs user in. As you can see I am using hooks. When I console.log login from useSelector hook, it console's the updated state. Then the useEffect hook gets called. But the problem is the login is not updating all the time. But still useEffect goes into a loop. What am I missing and how can I fix this?

UPDATE

Below is my reducer

import * as types from '../actions';

export default function(state = [], action) {
  const response = action.response;

  switch(action.type) {
    case types.LOGIN_USER_SUCCESS:
      return { ...state, response };
    case types.LOGIN_USER_ERROR:
      return { ...state, response };
    default:
      return state;
  }
};

Here is the action.

import * as types from './index';

export const loginUserAction = (user) => {
  return {
    type: types.LOGIN_USER,
    user
  }
};

Upvotes: 0

Views: 248

Answers (2)

Davit Gyulnazaryan
Davit Gyulnazaryan

Reputation: 811

A possible solution would be to destructure the object to make the comparison easier

  const {message = '', success = false, token = ''} = useSelector(state => state.login.response || {}); //should prevent the error, of response is undefined

  console.log(message, success);

  useEffect(() => {
    //there are other condition options like maybe if(message?.length)
    if (message) {
      setMessage(message);
    }

    // Can move setSuccess out of the if, to setSuccess even when it is falsy
    if (success) { //note that using isSuccess here might not work cause the state might be the old one still
        setSuccess(success)
        setCookie('token', token, 1);
      }
  }, [message, success, token]); //having scalar values (string and boolean) will prevent the loop.

Upvotes: 1

Blatzo
Blatzo

Reputation: 166

 if (login && !isSuccess) { // Here
      setSuccess(login.success);
      setMessage(login.message);

      if (isSuccess) {
        setCookie('token', login.token, 1);
      }
    }

Try to add this and see if this works


import { createSlice } from '@reduxjs/toolkit';

const initialState = {
    loading: false,
    error: null,
    user: null,
    isUserLogged: false,
};

const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers: {
        userAuthStart(state, action) {
            return {
                ...state,
                loading: true,
                error: null,
                user: null,
                isUserLogged: false,
            };
        },
        userAuthSuccess(state, { payload }) {
            return { ...state, loading: false, user: payload, isUserLogged: true };
        },
        userAuthFail(state, { payload }) {
            return { ...state, loading: false, error: payload, isUserLogged: false };
        },
        userLogout(state) {
            return {
                ...state,
                loading: false,
                error: null,
                user: null,
                isUserLogged: false,
            };
        },
    },
});

export const {
    userAuthStart,
    userAuthSuccess,
    userAuthFail,
    userLogout,
} = authSlice.actions;

export default authSlice.reducer;

I use @reduxjs/toolkit for redux.

You can declare and update state of isUserLogged or something to true if user is logged successfully. Then, you can use useSelector to use it inside components.

How to use

const { isUserLogged } = useSelector(state => state.auth)

Upvotes: 0

Related Questions