Search code examples
reactjsreduxreact-reduxlocal-storagedispatch

React-redux infinitely sends dispatch requests after header component updates


I am new to redux. On successful login, header re-renders but then there is infinite dispatch of USER_LOADED. I changed mapToProps from generic to specific imports but still not able to resolve this. I found one post that was referring to de-linking header from app.js but I did not get it. As in not sure what to do.

App.js

const App = () => {
  useEffect(() => {
    if (localStorage.token) {
      setAuthToken(localStorage.token);
    }
    store.dispatch(loadUser());

    window.addEventListener("storage", () => {
      if (!localStorage.token) store.dispatch({ type: LOGOUT });
    });
  }, []);

  return (
    <Provider store={store}>
      <Router>
        <Header />
        <ToastContainer />

        <Switch>
          <Route exact path="/" component={Landing} />
          <Route exact path="/login" component={Login} />
          {/* <Route exact path="/register" component={Register} /> */}
          <Route exact path="/userList" component={UserList} />
        </Switch>
      </Router>
    </Provider>
  );
};

export default App;

Header.js

const Header = ({ user, logout }) => {
  const [current, setCurrent] = useState("home");
  const handleClick = (e) => setCurrent(e.key);
  //const { user } = auth;

  return (
    <Menu onClick={handleClick} selectedKeys={[current]} mode="horizontal">
      <Item key="home" icon={<HomeOutlined />}>
        <Link to="/"> Training</Link>
      </Item>
      {!user && (
        <Item key="register" icon={<UserAddOutlined />} className="float-right">
          <Link to="/register">Register</Link>
        </Item>
      )}
      {!user && (
        <Item key="login" icon={<UserOutlined />} className="float-right">
          <Link to="/login">Login</Link>
        </Item>
      )}
      {user && (
        <SubMenu
          key="SubMenu"
          icon={<LoginOutlined />}
          title={(user.name && user.name) || (user.email && user.email)}
          className="float-right"
        >
          <Item icon={<LogoutOutlined />} onClick={logout}>
            Logout
          </Item>
        </SubMenu>
      )}
    </Menu>
  );
};
Header.propTypes = {
  user: PropTypes.object,
};
const mapStateToProps = (state) => ({
  user: state.auth.user,
});
export default connect(mapStateToProps, { logout })(Header);

Login.js

const Login = ({ loading, errors, login, history }) => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const getin = async (e) => {
    e.preventDefault();
    if (!email.trim() || !password.trim()) {
      toast.error("Both email and password are required");
      return;
    }

    login({ email, password }, history);
  };
  return (
    <div className="search-params">
      <form onSubmit={getin}>
        <label htmlFor="email">email</label>
        <input
          type="email"
          id="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <label htmlFor="password">Password</label>
        <input
          type="password"
          id="password"
          value={password}
          placeholder="minimum 6 characters"
          onChange={(e) => setPassword(e.target.value)}
        />
        <button type="submit" disabled={!email || !password || loading}>
          Submit
        </button>
      </form>
      {errors &&
        errors.map((error, index) => (
          <p key={index} className="text-danger">
            {error.msg}
          </p>
        ))}
    </div>
  );
};

Login.propTypes = {
  email: PropTypes.string,
  user: PropTypes.object,
  password: PropTypes.string,
};
const mapStateToProps = (state) => ({
  loading: state.auth.loading,
  isAuthenticated: state.auth.isAuthenticated,
  errors: state.auth.errors,
});

export default connect(mapStateToProps, { login })(Login);

Reducer

const initialState = {
  token: localStorage.getItem("token"),
  isAuthenticated: null,
  loading: true,
  user: null,
  errors: [],
};
const auth = (state = initialState, action) => {
  const { type, payload } = action;
  switch (type) {
    case dux.USER_LOADED:
      return {
        ...state,
        user: payload,
        isAuthenticated: true,
        loading: false,
        errors: [],
      };
    case dux.REGISTER_SUCCESS:
    case dux.LOGIN_SUCCESS:
      return {
        ...state,
        ...payload,
        isAuthenticated: true,
        loading: false,
        errors: [],
      };
    case dux.REGISTER_FAIL:
    case dux.LOGIN_FAIL:
      return {
        ...state,

        token: null,
        isAuthenticated: false,
        loading: false,
        user: null,
        errors: payload,
      };
    case dux.AUTH_ERROR:
    case dux.LOGOUT:
      return {
        ...state,
        token: null,
        user: null,
        isAuthenticated: false,
        loading: false,
        errors: [],
      };

    default:
      return state;
  }
};

export default auth;

actionCreators.js

import api from "../../utils/api";
import * as action from "./actionTypes";

export const loadUser = () => async (dispatch) => {
  try {
    const res = await api.get("/auth");
    dispatch({
      type: action.USER_LOADED,
      payload: res.data,
    });
    dispatch(loadUser());
  } catch (err) {
    dispatch({
      type: action.AUTH_ERROR,
    });
  }
};

//Login user
export const login = (formData, history) => async (dispatch) => {
  try {
    const res = await api.post("/auth", formData);
    console.log("res in login action", res);
    dispatch({
      type: action.LOGIN_SUCCESS,
      payload: res.data,
    });
    dispatch(loadUser());
    history.push("/userList");
  } catch (err) {
    console.log(err);
    dispatch({
      type: action.LOGIN_FAIL,
      payload: err.response.data.errors,
    });
  }
};

//Register User
export const register = (formData, history) => async (dispatch) => {
  try {
    const res = await api.post("/users", formData);
    dispatch({
      type: action.REGISTER_SUCCESS,
      payload: res.data,
    });
    dispatch(loadUser());
    history.push("/userList");
  } catch (err) {
    console.log(err);
    dispatch({
      type: action.REGISTER_FAIL,
      payload: err.response.data.errors,
    });
  }
};

export const logout = () => async (dispatch) =>
  dispatch({ type: action.LOGOUT });

Solution

  • In loadUser , I was recursively calling it. That caused the issue. But in these hours I learned that it is always better to pass specific items from redux store to improve performance. Thank you for quick responses guys.