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]解析重定向参数的方式存在明显缺陷:
- 若URL包含多个查询参数(如
?redirect=/checkout&foo=bar),split后会得到错误结果(/checkout&foo) - 无法兼容参数名变化、参数格式不规范的情况
- 未处理参数为空或不存在的边界场景
解决方案
使用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
相关产品推荐
相关产品推荐

