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

React V6电商平台登录授权重定向异常问题求助

电商网站用户授权重定向问题

我开发的电商网站在用户授权环节出现问题:用户登录后会错误重定向至首页。项目基于React V6开发,使用useNavigate、useLocation钩子,尝试通过location.search ? location.search.split("=")[1]:"/"获取重定向路径,但location.search始终无法正确获取目标路径,导致跳转错误。

相关代码

LoginScreen 代码

import React,{useState,useEffect} from 'react'
import {Link,useLocation,useNavigate}from 'react-router-dom'
import {Form, Button, Row, Col} from 'react-bootstrap'
import {useDispatch,useSelector} from 'react-redux'
import Message from '../Components/shared/Message'
import Loader from '../Components/shared/Loader'
import {login} from '../actions/userAction'
import FormContainer from '../Components/shared/FormContainer'

const LoginScreen = () => {
  const [email,setEmail] = useState('')
  const [password,setPassword] = useState('')
  const location = useLocation()
  const history = useNavigate()

  const redirect = location.search ? location.search.split("=")[1] : "/"; //问题出在这里
  console.log(redirect)

  const dispatch = useDispatch();
  const userLogin = useSelector((state) => state.userLogin);
  const { loading, error, userInfo } = userLogin;

  useEffect(() => {
    if(userInfo){
        history(redirect)
    }
  },[history,userInfo,redirect])

  const submitHandler =(e) =>{
    e.preventDefault()
    dispatch(login(email,password))

  }
 
  
    return (
        <>
            <FormContainer>
                
                <h1>SIGN IN</h1>
                {error && <Message variant="danger">{error}</Message>}
                {loading && <Loader/>}
                <Form onSubmit={submitHandler}>
                    <Form.Group controlId='email'>
                        <Form.Label>Email Address</Form.Label>
                        <Form.Control type='email' placeholder='enter email' value={email} onChange={(e) => setEmail(e.target.value)}></Form.Control>
                    </Form.Group>
                    <Form.Group controlId='password'>
                        <Form.Label>Password</Form.Label>
                        <Form.Control type='password' placeholder='enter password' value={password} onChange={(e) => setPassword(e.target.value)}></Form.Control>
                    </Form.Group>
                    <Button type='submit' variant='primary'>SIGN IN</Button>
                </Form>
                <Row>
                    <Col>
                        New Customer ? <Link to={redirect ? `register?redirect=${redirect}`: '/register'}>Register</Link>
                    </Col>
                </Row>
            </FormContainer>
        </>
  )
}

export default LoginScreen

userAction 代码

import axios from 'axios'
import {USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS, USER_LOGIN_FAIL} from '../constants/userConstants'

export const login = (email, password) => async (dispatch) => {
    try {
      dispatch({ type: USER_LOGIN_REQUEST });
      const config = { headers: { "Contnet-Type": "application/json" } };
      const { data } = await axios.post(
        "/api/users/login",
        { email,password },
        config
      );
      dispatch({
        type: USER_LOGIN_SUCCESS,
        payload: data,
      });
      localStorage.setItem("userInfo", JSON.stringify(data));
    } catch (error) {
      dispatch({
        type: USER_LOGIN_FAIL,
        payload:
          error.response && error.response.data.message
            ? error.response.data.message
            : error.message,
      });
    }
  };

userController 代码

const User = require('../models/UserModel')
const asyncHandler = require('express-async-handler') //TO AVOID TRY CATCH BLOCK REPEATEDLY WE USE HANDLER
const generateToken = require('../utils/generateToken')

const registerUser = asyncHandler(async (req, res) => {
    const { name, email, password } = req.body;
    const userExist = await User.findOne({ email });
    if (userExist) {
      res.status(400);
      throw new Error("User Already Exists!");
    }
  
    const user = await User.create({ name, email, password });
    if (user) {
      res.status(201).json({
        _id: user._id,
        name: user.name,
        email: user.email,
        isAdmin: user.isAdmin,
        token: generateToken(user._id),
      });
    } else {
      res.status(404);
      throw new Error("User Not Found");
    }
  });
  
  const authController = asyncHandler(async (req, res) => {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    if (user && (await user.matchPassword(password))) {
      res.json({
        _id: user._id,
        name: user.name,
        email: user.email,
        isAdmin: user.isAdmin,
        token: generateToken(user._id),
      });
    } else {
      res.status(401);
      throw new Error("Invalid Email or Password");
    }
  });
  
  const getUserProfile = asyncHandler(async (req, res) => {
    const user = await User.findById(req.user._id);
    if (user) {
      res.json({
        _id: user._id,
        name: user.name,
        email: user.email,
        isAdmin: user.isAdmin,
      });
    } else {
      res.status(404);
      throw new Error("User Not Found");
    }
  });

module.exports = {authController, getUserProfile, registerUser}

userRoute 代码

const express = require("express");
const {
  authController,
  getUserProfile,
  registerUser,
} = require("../controllers/usersController");
const { protect } = require("../middlewares/authMiddleware");

const router = express.Router();

//user registration
router.route("/").post(registerUser);

//post email and password auth
router.post("/login", authController);

//get user profile Private Route
router
  .route("/profile")
  .get(protect, getUserProfile);

module.exports = router;

userReducer 代码

import {USER_LOGIN_REQUEST, USER_LOGIN_SUCCESS, USER_LOGIN_FAIL, USER_LOGOUT} from '../constants/userConstants'

export const userLoginReducer = (state = {}, action) => {
    switch (action.type) {
      case USER_LOGIN_REQUEST:
        return { loading: true };
      case USER_LOGIN_SUCCESS:
        return { loading: false, userInfo: action.payload };
      case USER_LOGIN_FAIL:
        return { loading: false, error: action.payload };
      case USER_LOGOUT:
        return {};
      default:
        return state;
    }
  };

问题分析与解决方案

核心问题

你当前使用location.search.split("=")[1]解析重定向参数的方式存在明显缺陷:

  1. 若URL包含多个查询参数(如?redirect=/checkout&foo=bar),split后会得到错误结果(/checkout&foo)
  2. 无法兼容参数名变化、参数格式不规范的情况
  3. 未处理参数为空或不存在的边界场景

解决方案

使用React V6推荐的URLSearchParamsAPI规范解析查询参数,这是处理URL查询参数的标准方法,能彻底避免split带来的各类问题。

1. 修正参数解析逻辑

将原来的参数解析代码:

const redirect = location.search ? location.search.split("=")[1] : "/";

替换为:

const searchParams = new URLSearchParams(location.search);
const redirect = decodeURIComponent(searchParams.get('redirect') || '/');

添加decodeURIComponent是为了处理路径包含特殊字符的场景,确保重定向路径准确。

2. 优化注册链接的参数传递

注册链接的参数拼接也用URLSearchParams优化,避免手动拼接的潜在错误:

// 替换原Link组件
<Link to={() => {
  const params = new URLSearchParams();
  if (redirect && redirect !== '/') {
    params.set('redirect', redirect);
  }
  return `/register${params.toString() ? `?${params.toString()}` : ''}`;
}}>Register</Link>

3. 额外检查点

  • 确认触发登录跳转的页面(如需要登录的结算页)是否正确携带redirect参数,跳转URL应为/login?redirect=/checkout格式
  • 若跳转时路径包含特殊字符,需用encodeURIComponent编码,比如:
    navigate(`/login?redirect=${encodeURIComponent('/checkout')}`);
    

修正后的完整LoginScreen代码

import React,{useState,useEffect} from 'react'
import {Link,useLocation,useNavigate}from 'react-router-dom'
import {Form, Button, Row, Col} from 'react-bootstrap'
import {useDispatch,useSelector} from 'react-redux'
import Message from '../Components/shared/Message'
import Loader from '../Components/shared/Loader'
import {login} from '../actions/userAction'
import FormContainer from '../Components/shared/FormContainer'

const LoginScreen = () => {
  const [email,setEmail] = useState('')
  const [password,setPassword] = useState('')
  const location = useLocation()
  const history = useNavigate()

  // 修正后的参数解析
  const searchParams = new URLSearchParams(location.search);
  const redirect = decodeURIComponent(searchParams.get('redirect') || '/');
  console.log(redirect)

  const dispatch = useDispatch();
  const userLogin = useSelector((state) => state.userLogin);
  const { loading, error, userInfo } = userLogin;

  useEffect(() => {
    if(userInfo){
        history(redirect)
    }
  },[history,userInfo,redirect])

  const submitHandler =(e) =>{
    e.preventDefault()
    dispatch(login(email,password))

  }
 
  
    return (
        <>
            <FormContainer>
                
                <h1>SIGN IN</h1>
                {error && <Message variant="danger">{error}</Message>}
                {loading && <Loader/>}
                <Form onSubmit={submitHandler}>
                    <Form.Group controlId='email'>
                        <Form.Label>Email Address</Form.Label>
                        <Form.Control type='email' placeholder='enter email' value={email} onChange={(e) => setEmail(e.target.value)}></Form.Control>
                    </Form.Group>
                    <Form.Group controlId='password'>
                        <Form.Label>Password</Form.Label>
                        <Form.Control type='password' placeholder='enter password' value={password} onChange={(e) => setPassword(e.target.value)}></Form.Control>
                    </Form.Group>
                    <Button type='submit' variant='primary'>SIGN IN</Button>
                </Form>
                <Row>
                    <Col>
                        New Customer ? 
                        <Link to={() => {
                          const params = new URLSearchParams();
                          if (redirect && redirect !== '/') {
                            params.set('redirect', redirect);
                          }
                          return `/register${params.toString() ? `?${params.toString()}` : ''}`;
                        }}>
                          Register
                        </Link>
                    </Col>
                </Row>
            </FormContainer>
        </>
  )
}

export default LoginScreen

内容的提问来源于stack exchange,提问作者Chetanraj Patil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 05:46:56