反应路由器 dom 在完成操作之前推送用户

react router dom pushing user before finishing action

我正在使用 MERN 构建一个 Reddit 克隆,并在前端使用 redux 和 React

所以问题是当用户尝试注册时,它不会将用户推送到“/login”路径。这不是 redux 的问题(我认为),因为在使用不同的电子邮件再次按下“注册”按钮后它可以工作

这里还是redux代码以防万一

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;

问题是 handleOnClick 没有等到执行 redux 存储更新。因此,history.push() 在第一种情况下不 运行。

当您再次点击提交时,存储中已经存在之前的状态。因此 if 条件被执行。

要解决它,您必须添加一个 useEffect() 钩子,它会在用户注册后重定向。

例子

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");
    //}
 };