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

React Router 6.4.3数据路由Action如何调用认证Hook的Login方法?

问题描述

我正在使用React Router 6.4.3的新数据路由功能,某第三方认证库通过Hook暴露了AuthenticationContext和login函数,使用方式如下:

const { AuthenticationContext, login } = useAuthentication();

我已经通过createBrowserRouter中的无路径路由将所有路由包裹在AuthenticationContext中,但不清楚如何利用路由Action执行登录并完成跳转——因为路由配置和Action不在函数组件内,无法使用useAuthentication Hook。

我的路由配置:

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { AuthProvider } from 'auth';
import Login, { action as loginAction } from './routes/login';

const router = createBrowserRouter([
    {
        element: <AuthProvider />,
        children: [
            {
                path: '/login',
                element: <Login />,
                action: loginAction,
            },
        ],
    },
]);

function App() {
    return (
        <RouterProvider router={router} />
    );
}

当前loginAction仅为占位实现:

const action = async ({ request, params }) => {
    const formData = await request.formData();
    const updates = Object.fromEntries(formData);

    // 如何在这里调用useAuthentication()暴露的login()方法?
}

AuthenticationContext及相关方法大致如下:

const loginApi = async (email, password) => {
   // 调用登录接口的ajax请求
}

const AuthContext = React.createContext(null);

const AuthProvider = () => {
    const [jwt, setJwt] = useState(null);
    const navigate = useNavigate();

    const login = async (email, password) => {
        const jwt = await loginApi(email, password);
        setJwt(jwt);
        navigate('/dashboard');
    }

    const contextValue = {
        jwt,
        login: login, // 修正原代码笔误:handleLogin改为login
    }
 
    return (
        <AuthContext.Provider value={contextValue}>
            <Outlet />
        </AuthContext.Provider>
    );
}

const useAuth = () => {
    return useContext(AuthContext);
}

export default AuthProvider;
export { useAuth };

请问是否有无需修改该认证库即可适配数据路由的方案?


解决方案

以下是几种无需修改认证库核心代码的适配方案:

方案一:在Login组件内处理表单提交

直接在Login组件中通过useAuth获取login方法,接管表单提交逻辑,路由Action可留空或仅做简单兼容处理:

// Login组件
import { useAuth } from '../auth';
import { Form } from 'react-router-dom';

const Login = () => {
  const { login } = useAuth();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const email = formData.get('email') as string;
    const password = formData.get('password') as string;
    
    await login(email, password);
  };

  return (
    <Form method="post" onSubmit={handleSubmit}>
      <input type="email" name="email" required placeholder="邮箱" />
      <input type="password" name="password" required placeholder="密码" />
      <button type="submit">登录</button>
    </Form>
  );
};

// 空实现的Action,保留以兼容路由配置
export const action = async () => {
  return null;
};

export default Login;

方案二:Action处理验证,组件内调用登录

如果需要在路由Action中做表单数据验证或预处理,可以在Action中完成逻辑后返回数据,再在Login组件中根据返回结果调用login方法:

修改loginAction:

const action = async ({ request }) => {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;

  // 前端基础验证
  if (!email.includes('@') || password.length < 6) {
    return { error: '请输入有效的邮箱和不少于6位的密码' };
  }

  // 返回处理后的表单数据给组件
  return { email, password };
};

修改Login组件:

import { useAuth } from '../auth';
import { Form, useActionData } from 'react-router-dom';
import { useEffect } from 'react';

const Login = () => {
  const { login } = useAuth();
  const actionData = useActionData() as { error?: string; email?: string; password?: string };

  useEffect(() => {
    // 如果Action返回了合法的登录数据,调用login方法
    if (actionData?.email && actionData?.password) {
      login(actionData.email, actionData.password);
    }
  }, [actionData, login]);

  return (
    <Form method="post">
      {actionData?.error && <p style={{ color: 'red' }}>{actionData.error}</p>}
      <input type="email" name="email" required placeholder="邮箱" />
      <input type="password" name="password" required placeholder="密码" />
      <button type="submit">登录</button>
    </Form>
  );
};

export { action };
export default Login;

方案三:抽离登录API,Action直接调用并同步状态

将认证库中的loginApi单独导出(如果原库未导出,可复制一份独立函数),在路由Action中直接调用API,通过本地存储同步AuthProvider的状态:

1. 导出或复制loginApi:

// 从auth.ts中导出,或单独创建文件
export const loginApi = async (email: string, password: string) => {
  // 原认证库中的登录接口调用逻辑
  const response = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify({ email, password }),
    headers: { 'Content-Type': 'application/json' },
  });
  return response.json();
};

2. 修改loginAction:

import { redirect } from 'react-router-dom';
import { loginApi } from '../auth';

const action = async ({ request }) => {
  const formData = await request.formData();
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;

  try {
    const jwt = await loginApi(email, password);
    // 将jwt存入localStorage,供AuthProvider同步
    localStorage.setItem('jwt', jwt);
    // 重定向到仪表盘
    return redirect('/dashboard');
  } catch (err) {
    return { error: '登录失败,请检查账号密码' };
  }
};

3. 修改AuthProvider监听存储变化:

const AuthProvider = () => {
  // 初始化时从localStorage读取jwt
  const [jwt, setJwt] = useState(localStorage.getItem('jwt'));
  const navigate = useNavigate();

  useEffect(() => {
    // 监听localStorage变化,同步jwt状态
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === 'jwt') {
        setJwt(e.newValue);
      }
    };
    window.addEventListener('storage', handleStorageChange);
    return () => window.removeEventListener('storage', handleStorageChange);
  }, []);

  const login = async (email, password) => {
    const jwt = await loginApi(email, password);
    localStorage.setItem('jwt', jwt);
    setJwt(jwt);
    navigate('/dashboard');
  }

  const contextValue = {
    jwt,
    login,
  }
 
  return (
    <AuthContext.Provider value={contextValue}>
      <Outlet />
    </AuthContext.Provider>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 08:05:26