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

React Router 6.3.0中如何将Login组件的state传递给RequireAuth组件

解决React Router 6中受保护路由共享登录状态的问题

你的核心问题是Login组件内部的user和isLoading状态无法直接传递给RequireAuth组件——二者属于不同路由下的组件,没有直接的父子props传递关系。下面给出两种可行的解决方案:


方案一:使用React Context全局管理认证状态

通过创建全局Context来共享用户信息和加载状态,让所有组件都能访问到认证相关数据。

1. 创建AuthContext

新建AuthContext.js文件:

import { createContext, useContext, useState, useEffect } from 'react';
import axios from 'axios';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [isAuthLoading, setIsAuthLoading] = useState(true);

  // 页面刷新时自动验证token,恢复用户状态
  useEffect(() => {
    const initAuth = async () => {
      const token = localStorage.getItem('access_token');
      if (token) {
        try {
          const res = await axios.get('http://www.example.com/auth/me', {
            headers: { Authorization: `Bearer ${token}` }
          });
          setUser(res.data);
        } catch (err) {
          // token失效,清除存储
          localStorage.removeItem('access_token');
          localStorage.removeItem('refresh_token');
        }
      }
      setIsAuthLoading(false);
    };
    initAuth();
  }, []);

  return (
    <AuthContext.Provider value={{ user, setUser, isAuthLoading, setIsAuthLoading }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义钩子,简化Context访问
export function useAuth() {
  return useContext(AuthContext);
}

2. 用AuthProvider包裹App组件

修改App.js,确保所有组件都能访问Context:

import { AuthProvider } from './path/to/AuthContext';
import { Routes, Route } from 'react-router-dom';
import RequireAuth from './path/to/RequireAuth';
import Dashboard from './path/to/Dashboard';
import Products from './path/to/Products';
import CreateProduct from './path/to/CreateProduct';
import Login from './path/to/Login';
import { ToastContainer } from 'react-toastify';

function App() {
  return (
    <AuthProvider>
      <div className="App">
        <Routes>
          {/* 外层路由统一加保护,子路由无需重复添加 */}
          <Route path="/" element={<RequireAuth><Dashboard /></RequireAuth>}>
            <Route path="products" element={<Products />} />
            <Route path="products/create" element={<CreateProduct />} />
          </Route>
          <Route path="/login" element={<Login />} />
        </Routes>
        <ToastContainer />
      </div>
    </AuthProvider>
  );
}

export default App;

3. 修改Login组件,更新全局状态

import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { useLocation, useNavigate } from "react-router-dom";
import useToken from "../../hooks/useToken";
import Loading from "../shared/Loading";
import { useAuth } from '../../path/to/AuthContext';

const Login = () => {
  const { setUser, setIsAuthLoading } = useAuth();
  const [error, setError] = useState("");
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || "/";
  const { register, handleSubmit, formState: { errors } } = useForm();
  let errorElement;

  const userLogin = async (data) => {
    const response = await axios.post(
      "http://www.example.com/auth/login",
      data
    );
    return response.data;
  };

  const { mutate, isLoading, isError } = useMutation(userLogin, {
    onSuccess: (data) => {
      setUser(data);
      // 登录成功后跳转到之前访问的页面
      navigate(from, { replace: true });
    },
    onError: (error) => {
      setError(error);
    },
    onMutate: () => {
      setIsAuthLoading(true);
    },
    onSettled: () => {
      setIsAuthLoading(false);
    }
  });

  if (isLoading) {
    return <Loading />;
  }

  if (isError) {
    errorElement = (
      <p className="px-1 pb-2">
        <small className="text-red-500">Invalid user credentials</small>
      </p>
    );
  }

  const onSubmit = (data) => {
    mutate(data);
  };

  return (
    {/* 你的表单JSX代码 */}
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* 表单字段 */}
      {errorElement}
      <button type="submit">登录</button>
    </form>
  );
};

export default Login;

4. 修改RequireAuth组件,从Context获取状态

import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import Loading from "../Shared/Loading";
import { useAuth } from '../path/to/AuthContext';

function RequireAuth({ children }) {
  const location = useLocation();
  const { user, isAuthLoading } = useAuth();

  // 认证校验中显示加载
  if (isAuthLoading) {
    return <Loading />;
  }

  // 未登录则跳转到登录页
  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

export default RequireAuth;

方案二:利用TanStack Query缓存共享用户状态

既然你已经在使用TanStack Query,可以直接用它的缓存来存储用户信息,无需额外创建Context。

1. 修改Login组件,将用户信息存入Query缓存

import { useMutation, useQueryClient } from "@tanstack/react-query";
// ...其他导入

const Login = () => {
  const queryClient = useQueryClient();
  // ...其他代码

  const { mutate, isLoading, isError } = useMutation(userLogin, {
    onSuccess: (data) => {
      // 将用户信息存入Query缓存,key为['user']
      queryClient.setQueryData(['user'], data);
      navigate(from, { replace: true });
    },
    // ...其他回调
  });

  // ...剩余代码
};

2. 修改RequireAuth组件,从Query缓存获取用户信息

import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import Loading from "../Shared/Loading";
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

function RequireAuth({ children }) {
  const location = useLocation();

  // 从缓存获取用户信息,缓存不存在时自动请求验证
  const { data: user, isLoading } = useQuery(['user'], async () => {
    const token = localStorage.getItem('access_token');
    if (!token) return null;
    // 请求用户信息接口验证token有效性
    const res = await axios.get('http://www.example.com/auth/me', {
      headers: { Authorization: `Bearer ${token}` }
    });
    return res.data;
  }, {
    staleTime: Infinity, // 避免重复请求
    retry: false
  });

  if (isLoading) {
    return <Loading />;
  }

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

export default RequireAuth;

关键说明

  1. 避免重复嵌套RequireAuth:外层路由添加RequireAuth后,所有子路由会自动继承保护,无需每个子路由都包裹。
  2. 页面刷新状态保留:两种方案都处理了刷新页面后的用户状态恢复问题,通过读取localStorage中的token重新验证。
  3. 加载状态区分:Login中的isLoading是登录请求的加载状态,而RequireAuth中的加载是认证校验的状态,二者逻辑不同,全局管理更合理。

内容的提问来源于stack exchange,提问作者Thakur Saad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 19:05:24