You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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函数存在两个核心问题:

  1. 首次请求OTP的用户(else分支)仅保存了OTP记录,但未发送邮件,也没有返回任何API响应,导致前端请求一直处于pending状态,无法触发跳转逻辑。
  2. 已有OTP记录的用户(existEmail分支)在邮件发送成功后,res.status()未指定状态码和响应内容,前端无法收到完成的响应信号,因此不执行跳转。

修复方案:

  • 提取发送邮件的逻辑为通用函数,确保两种分支都能正常发送邮件
  • 所有分支都补充明确的响应状态码和内容,保证前端能收到完整的API响应,进而触发跳转

内容的提问来源于stack exchange,提问作者Robert Downey Jr.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 11:21:03