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&®isteredUser.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"); //} };