反应路由器 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&®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");
//}
};
我正在使用 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&®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");
//}
};