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
相关产品推荐
相关产品推荐

