React应用中useNavigate Hook失效:发送OTP后无法跳转页面
问题:发送OTP邮件后前端无法跳转页面
我正在开发一个注册后通过OTP登录的页面,OTP通过后端nodemailer模块发送。用Postman测试REST API功能正常,但前端发送邮件后无法跳转到OTP验证页面。
相关代码
前端Login组件
import "../styles/mix.css"; import { Navigate, NavLink, useNavigate } from "react-router-dom"; import { useState } from "react"; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { sentOtpFunction } from "../services/Apis"; export default function Login() { const [email, setEmail] = useState(""); const navigate = useNavigate(); // 发送OTP const sendOtp = async (e) => { e.preventDefault(); if (email === "") { toast.error("请输入邮箱!"); } else if (!email.includes('@')) { toast.error("请输入有效的邮箱!"); } else { const data = { email }; const response = await sentOtpFunction(data); console.log(response); if (response.status === 200) { navigate("/user/otp"); } else { toast.error(response.response.data.error); } } } return ( <> <section> <div className="form_data"> <div className="form_heading"> <h1>欢迎回来,登录</h1> <p>嗨,很高兴你回来,请登录。</p> </div> <form> <div className="form_input"> <label htmlFor="email">邮箱</label> <input type="email" name="email" onChange={(e) => setEmail(e.target.value)} placeholder="输入你的邮箱地址" /> </div> <button className="btn" onClick={sendOtp}>登录</button> <p>没有账号?<NavLink to="/register">注册</NavLink></p> </form> </div> <ToastContainer /> </section> </> ) }
前端sendOtpFunction
import { commonrequest } from './ApiCall'; import { BACKEND_URL } from './helper'; export const registerfunction = async (data) => { return await commonrequest("POST", `${BACKEND_URL}/user/register`, data) } export const sentOtpFunction = async (data) => { return await commonrequest("POST", `${BACKEND_URL}/user/sendotp`, data) }
前端apicall.js
import axios from 'axios'; export const commonrequest = async (methods, url, body, header) => { let config = { method: methods, url, headers: header ? header : { "Content-Type": "application/json" }, data: body } return axios(config).then((data) => { return data }).catch((error) => { return error }) }
后端api.js
require("dotenv").config(); const express = require('express'); const app = express(); const cors = require('cors'); require("./db/conn"); const router = require("./routes/router"); const PORT = 4002; // 中间件 app.use(express.json()); app.use(cors()); app.use(router); app.listen(PORT, () => console.log("服务器已连接!"));
后端routes.js
const express = require("express"); const router = new express.Router(); const controllers = require("../controllers/userControllers"); // 路由 router.post("/user/register", controllers.userregister); router.post("/user/sendotp", controllers.userOtpSend); module.exports = router;
修复后的后端userControllers.js
const users = require("../models/userSchema") const userotp = require("../models/userOtp"); const nodemailer = require("nodemailer"); // 邮箱配置 const transporter = nodemailer.createTransport({ service: "gmail", host: 'smtp.gmail.com', port: 465, secure: true, auth: { user: process.env.EMAIL, pass: process.env.PASSWORD } }) exports.userregister = async (req, res) => { const { fname, email, password } = req.body; if (!fname || !email || !password) { res.status(400).json({ error: "请填写所有输入项" }); } try { const preuser = await users.findOne({ email: email }); if (preuser) { res.status(400).json({ error: "该用户已存在于数据库中" }) } else { const userregister = new users({ fname, email, password }); // 密码哈希处理 const storeData = await userregister.save(); res.status(200).json(storeData); } } catch (error) { res.status(400).json({ error: "无效的信息" }) } } // 发送OTP exports.userOtpSend = async (req, res) => { const { email } = req.body; if (!email) { res.status(400).json({ error: "请输入你的邮箱" }) } try { const preuser = await users.findOne({ email: email }); if (preuser) { const OTP = Math.floor(100000 + Math.random() * 900000); const existEmail = await userotp.findOne({ email: email }); // 封装发送邮件的通用逻辑 const sendVerificationMail = () => { const mailOptions = { from: process.env.EMAIL, to: email, subject: "OTP验证邮件", text: `你的OTP验证码是:${OTP}` } transporter.sendMail(mailOptions, (error, info) => { if (error) { console.log("邮件发送失败", error); res.status(400).json({ error: "邮件发送失败" }); } else { console.log("邮件已发送", info.response); res.status(200).json({ success: "OTP已发送至你的邮箱" }); } }) } if (existEmail) { // 更新已有用户的OTP const updateData = await userotp.findByIdAndUpdate( { _id: existEmail._id }, { otp: OTP }, { new: true } ); await updateData.save(); sendVerificationMail(); } else { // 为新请求用户保存OTP并发送邮件 const saveOtpData = new userotp({ email, otp: OTP }); await saveOtpData.save(); sendVerificationMail(); } } else { res.status(400).json({ error: "该用户不存在于数据库中" }); } } catch (error) { res.status(400).json({ error: "无效的信息", error }) } }
问题原因及修复说明
原后端userOtpSend函数存在两个核心问题:
- 首次请求OTP的用户(
else分支)仅保存了OTP记录,但未发送邮件,也没有返回任何API响应,导致前端请求一直处于pending状态,无法触发跳转逻辑。 - 已有OTP记录的用户(
existEmail分支)在邮件发送成功后,res.status()未指定状态码和响应内容,前端无法收到完成的响应信号,因此不执行跳转。
修复方案:
- 提取发送邮件的逻辑为通用函数,确保两种分支都能正常发送邮件
- 所有分支都补充明确的响应状态码和内容,保证前端能收到完整的API响应,进而触发跳转
内容的提问来源于stack exchange,提问作者Robert Downey Jr.
相关产品推荐
相关产品推荐

