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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:50:24