如何在Router v6.4与Redux中派发Action实现登录反馈?
问题描述
我需要在登录成功时派发showSnackbar action显示成功反馈,登录出错时派发错误消息。已创建管理snackbar状态的reducer,但不清楚该如何、在何处执行dispatch操作——当前在Router的action函数和login工具函数里都无法直接调用dispatch。
现有代码实现
main.jsx(带路由配置)
const router = createBrowserRouter([{ path: "/login", element: <LoginPage/>, errorElement: <ErrorPage/>, action: loginAction }, { path: "*", element: <h1>404 Page not found...</h1>, }]); ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <Provider store={store}> <RouterProvider router={router}/> </Provider> </React.StrictMode>, )
LoginPage.jsx
function LoginPage() { const navigation = useNavigation(); return ( <> <main> <LoginForm submitting={navigation.state === "submitting"}/> </main> </> ) } export default LoginPage; export async function action({request}) { // dispatch not possible const formData = await request.formData(); const user = { username: formData.get("username"), password: formData.get("password") } try { await login(user); } catch (error){ return error } return redirect('/'); }
LoginForm.jsx
function LoginForm({submitting}) { return ( <> <div className={classes.container}> <Form method="post" action={'/login'} className={classes.form}> <h1>Ocean 4 HEALTH</h1> <fieldset> <label htmlFor="username">Username</label> <input id="username" name="username" type="text" required={true} minLength={5} maxLength={30}/> </fieldset> <fieldset> <label htmlFor="password">Password</label> <input id="password" name="password" type="password" required={true} minLength={8} maxLength={30}/> </fieldset> <button disabled={submitting}> {submitting ? "Submitting ..." : "Login"} </button> </Form> </div> </> ) }
login.js
export async function login(user) { // dispatch not possible const response = await fetch(`${BASE_URL}/auth/login`, { method: "POST", body: JSON.stringify({ codF: user.username.toUpperCase(), ...user }), headers: { "Content-Type": " application/json", }, }); if (!response.ok) { throw {message: "Ops, il login non è andato a buon fine", status: 500}; } if (response.status === 226) { throw {message: "Ops, hai già una sessione aperta su un pc o su un altro tab...", status: 226}; } sessionStorage.setItem("token", JSON.stringify(user)); }
snackbar reducer
const snackbarSlice = createSlice({ name: 'snackbar', initialState: { show: false, message: "", type: "" }, reducers: { showSnackbar(state, action) { state.show = true; state.message = action.payload.message; state.type = action.payload.type; }, removeSnackbar(state) { state.show = false; } } }) export const snackbarActions = snackbarSlice.actions; export default snackbarSlice
解决方案
核心思路是在LoginPage组件内处理反馈:组件处于Redux Provider中,可直接使用useDispatch;同时借助React Router的useActionData钩子获取路由action的返回结果,根据结果触发snackbar的action。
步骤1:修改LoginPage组件
import { useNavigation, useActionData } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { snackbarActions } from './path/to/snackbarSlice'; import { useEffect } from 'react'; function LoginPage() { const navigation = useNavigation(); const actionData = useActionData(); const dispatch = useDispatch(); useEffect(() => { if (!actionData) return; // 登录失败:显示错误提示 if (actionData.message) { dispatch(snackbarActions.showSnackbar({ message: actionData.message, type: 'error' })); } // 登录成功:显示成功提示并跳转 else if (actionData.success) { dispatch(snackbarActions.showSnackbar({ message: '登录成功!', type: 'success' })); // 延迟跳转,确保用户看到提示 setTimeout(() => { navigation.navigate('/'); }, 2000); } }, [actionData, dispatch, navigation]); return ( <> <main> <LoginForm submitting={navigation.state === "submitting"}/> </main> </> ) } export default LoginPage; // 修改路由action,不再直接redirect,返回状态标识 export async function action({request}) { const formData = await request.formData(); const user = { username: formData.get("username"), password: formData.get("password") } try { await login(user); // 返回成功状态 return { success: true }; } catch (error){ // 返回错误信息 return error; } }
步骤2:全局渲染Snackbar组件
创建Snackbar组件并放在全局位置(比如main.jsx中),监听Redux状态并显示:
import { useSelector, useDispatch } from 'react-redux'; import { snackbarActions } from './path/to/snackbarSlice'; import { useEffect } from 'react'; function Snackbar() { const { show, message, type } = useSelector(state => state.snackbar); const dispatch = useDispatch(); const handleClose = () => { dispatch(snackbarActions.removeSnackbar()); }; // 自动隐藏提示 useEffect(() => { if (show) { const timer = setTimeout(() => { dispatch(snackbarActions.removeSnackbar()); }, 3000); return () => clearTimeout(timer); } }, [show, dispatch]); return ( show && ( <div className={`snackbar snackbar-${type}`}> <span>{message}</span> <button onClick={handleClose}>×</button> </div> ) ); } export default Snackbar;
在main.jsx中添加该组件:
ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <Provider store={store}> <RouterProvider router={router}/> <Snackbar /> </Provider> </React.StrictMode>, )
说明
- 路由action无法直接访问Redux store,因此将反馈逻辑移到组件内处理,组件通过
useActionData获取action执行结果。 - 登录成功时延迟跳转,确保用户能看到成功提示;错误时页面不跳转,直接显示错误提示。
- Snackbar组件支持自动隐藏和手动关闭,提升用户体验。
内容的提问来源于stack exchange,提问作者Enrico Arnaudo
相关产品推荐
相关产品推荐

