Skip to content
Advertisement

react router dom pushing user before finishing action

I’m building a Reddit clone with MERN and am using redux and react in frontend

So the problem is when a user tries to register it dosen’t push the user to “/login” route. This is not an issue with redux (i think) because after pressing the “register” button again with a different email it works

still here is the redux code just in case

state/actions/auth.js

import axios from "../../axios/axios";

export const login = (email, password) => async (dispatch) => {
  try {
    const { data, headers } = await axios.post("/auth/login", {
      email,
      password,
    });

    dispatch({ type: "LOGIN", payload: headers });
    dispatch({ type: "SET_USER", payload: data });
    dispatch({ type: "CLEAR_ERRORS" });
  } catch (error) {
    dispatch({ type: "SET_ERROR", payload: error.response.data });
  }
};

export const register = (name, email, password) => async (dispatch) => {
  try {
    const { data } = await axios.post("/auth/register", {
      name,
      email,
      password,
    });

    dispatch({ type: "REGISTER", payload: data });
    dispatch({ type: "CLEAR_ERRORS" });
  } catch (error) {
    dispatch({ type: "SET_ERROR", payload: error.response.data });
  }
};

state/reducers/authReducer.js

const initailState = {
  isLoggedIn: false,
  token: null,
};

export default (state = initailState, action) => {
  switch (action.type) {
    case "LOGIN":
      return { isLoggedIn: true, token: action.payload.token };
    case "REGISTER":
      return { isLoggedIn: false, token: null, redirect: true };
    default:
      return state;
  }
};

state/reducers/errorReducer.js

const initailState = {
  message: null,
};

export default (state = initailState, action) => {
  switch (action.type) {
    case "CLEAR_ERRORS":
      return { message: null };
    case "SET_ERROR":
      return { message: action.payload };
    case "GET_ERRORS":
      state = initailState;
      return state;
    default:
      return state;
  }
};

state/reducers/index.js

import { combineReducers } from "redux";

import auth from "./authReducer";
import user from "./userReducer";
import error from "./errorReducer";

export default combineReducers({
  auth,
  user,
  error,
});

/pages/Register.js

import React, { useState } from "react";

import RemoveRedEyeOutlinedIcon from "@mui/icons-material/RemoveRedEyeOutlined";
import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined";

import { useDispatch, useSelector } from "react-redux";
import { useHistory, Link, Redirect } from "react-router-dom";
import { register } from "../state/actions/auth";

function Register() {
  const dispatch = useDispatch();
  const history = useHistory();

  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loading, setLoading] = useState(false);
  const [visible, setVisible] = useState(false);

  const error = useSelector((state) => state.error);
  const registeredUser = useSelector((state) => state.auth);

  const handleOnClick = (e) => {
    e.preventDefault();

    setLoading(true);
    dispatch(register(username, email, password));
    setLoading(false);

    if (registeredUser.redirect) {
      history.push("/login");
    }
  };

  if (loading) {
    return <h1>Loading</h1>;
  }

  return (
    <div className="h-screen flex items-center justify-center bg-gradient-to-bl from-[#f59d7d] to-[#fab8a0]">
      <div className="shadow-2xl px-4 pb-3 pt-3 mx-1 bg-orange-200 rounded-md max-w-md md:max-w-lg lg:max-w-2xl">
        <h1 className="text-2xl uppercase">signup</h1>
        <p className="text-gray-600 text-sm mt-2">Welcome to Reddit!</p>

        <p className="text-gray-600 text-xs mt-4">
          By continuing, you are setting up a Reddit account and agree to our{" "}
          <span className="text-blue-500 hover:underline cursor-pointer">
            User Agreement
          </span>{" "}
          and{" "}
          <span className="text-blue-500 hover:underline cursor-pointer">
            Privacy Policy
          </span>
          .
        </p>
        <form className="space-y-2 mt-5">
          <input
            className="auth-input"
            type="text"
            placeholder="Username"
            autoComplete="on"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
          <input
            className="auth-input"
            type="text"
            placeholder="Email"
            autoComplete="on"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
          <div className="flex items-center bg-white rounded-md focus-within:ring-1 focus-within:ring-orange-500">
            <input
              className="px-2 py-1 w-full rounded-md outline-none text-slate-700"
              placeholder="Password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              type={visible ? `text` : `password`}
            />
            <div className={visible ? `hidden` : `block`}>
              <RemoveRedEyeOutlinedIcon
                onClick={() => setVisible(!visible)}
                className="mr-4 text-gray-600 cursor-pointer"
              />
            </div>
            <div className={visible ? `block` : `hidden`}>
              <VisibilityOffOutlinedIcon
                onClick={() => setVisible(!visible)}
                className="mr-4 text-gray-600 cursor-pointer"
              />
            </div>
          </div>

          {error.message && (
            <h1 className="text-sm text-red-500 text-center">
              {error.message}
            </h1>
          )}

          <button
            type="submit"
            className="bg-blue-500 py-2 w-[70%] rounded-full text-center mx-14 md:mx-15 lg:mx-16 hover:bg-blue-600 transition-colors duration-300"
            onClick={handleOnClick}
          >
            <h1 className="font-bold text-white">Register</h1>
          </button>
        </form>
        <h1 className="text-xs text-slate-600 mt-4 text-center">
          Already a redditor?{" "}
          <Link
            to="/login"
            className="font-bold text-blue-500 text-xs hover:underline uppercase"
          >
            log in
          </Link>
        </h1>
      </div>
    </div>
  );
}

export default Register;

/backend/authentication/auth.js

const router = require("express").Router();
const User = require("../models/User");
const bcrypt = require("bcrypt");
const Jwt = require("jsonwebtoken");

const { registerValidation, loginValidation } = require("./validation");

router.post("/register", async (req, res) => {
  try {
    // Checking if the user already exists
    const userEmail = await User.findOne({ email: req.body.email });
    if (userEmail) {
      return res.status(400).json("Email already exists!");
    }

    // Validating data
    const { error } = registerValidation(req.body);
    if (error) {
      return res.status(400).json(error.details[0].message);
    }

    // Hashing password
    const salt = await bcrypt.genSalt(10);
    const hashedPass = await bcrypt.hash(req.body.password, salt);

    // Getting info for new user
    const user = new User({
      name: req.body.name,
      email: req.body.email,
      password: hashedPass,
    });

    // Saving new user
    const newUser = await user.save();

    res.json(newUser);
  } catch (err) {
    res.status(500).json(err);
  }
});

router.post("/login", async (req, res) => {
  try {
    // Validating data
    const { error } = loginValidation(req.body);
    if (error) {
      return res.status(400).json(error);
    }

    // Checking if the email is correct
    const userEmail = await User.findOne({ email: req.body.email });
    if (!userEmail) return res.status(400).json("Invalid email or password!");

    // Checking if the password is correct
    const validPass = await bcrypt.compare(
      req.body.password,
      userEmail.password
    );
    if (!validPass) return res.status(400).json("Invalid email or password!");

    // Creating and assigning the token
    const token = Jwt.sign({ _id: userEmail._id }, process.env.TOKEN_SECRET);
    res.header("auth-token", token).json({
      name: userEmail.name,
      email: userEmail.email,
      profilePic: userEmail.profilePic,
      upVotedPosts: userEmail.upVotedPosts,
      downVotedPosts: userEmail.downVotedPosts,
      joinedSubreddits: userEmail.joinedSubreddits,
      karma: userEmail.karma,
      date: userEmail.date,
      _id: userEmail._id,
      token: token,
    });
  } catch (err) {
    res.status(500).json(err);
  }
});

module.exports = router;

Advertisement

Answer

The issue is that handleOnClick doesnt wait till the execution of redux store update. Therefore, the history.push() doesnt run in the first case.

When you click submit again, there is already previous state in store. Therefore the if condition gets executed.

To resolve it you have to add a useEffect() hook which would redirect user after they register.

Example

useEffect(()=>{
  if(loading&&registeredUser.redirect){  
    setLoading(false)
     history.push("/login");
   }
    
},[loading,registeredUser]);


const handleOnClick = (e) => {
    e.preventDefault();

    setLoading(true);
    dispatch(register(username, email, password));
    // setLoading(false); ------> moved to useEffect

    //if (registeredUser.redirect) {  ------> moved to useEffect
    // history.push("/login");
    //}
 };
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement