React Router 6加载资源时如何在当前页面展示错误而非跳转
解决React Router加载错误时在当前页面展示提示的问题
核心思路
React Router的errorElement是用来处理致命路由错误(比如loader抛出异常、组件渲染崩溃)的,会触发页面跳转。要实现当前页展示非致命错误(如422),需要:
- 在loader中不抛出非致命错误,而是将错误信息作为返回值的一部分
- 在组件内部读取loader返回的结果,判断并展示提示组件(snackbar/提示框)
具体实现步骤
1. 修改Loader逻辑,返回错误信息而非抛出异常
把非致命错误(如422、400)的处理改为返回包含错误字段的对象,只对致命错误(如500、404)抛出异常:
async function rootLoader() { try { const res = await fetch('/api/data'); if (!res.ok) { const errorData = await res.json(); // 非致命错误:返回错误信息,不抛出 if (res.status === 422 || res.status === 400) { return { error: errorData.message, status: res.status }; } // 致命错误:抛出异常,触发errorElement throw new Response(JSON.stringify(errorData), { status: res.status }); } const data = await res.json(); return { data }; } catch (err) { // 网络错误等致命情况,抛出触发errorElement throw err; } }
2. 在组件内部处理错误并展示提示
在Root或子组件中使用useLoaderData获取结果,判断是否存在错误,然后渲染提示组件:
import { useLoaderData } from 'react-router-dom'; import { useState, useEffect } from 'react'; import Snackbar from './Snackbar'; // 自定义提示组件 function Root() { const loaderResult = useLoaderData(); const [showSnackbar, setShowSnackbar] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(''); useEffect(() => { if (loaderResult.error) { setSnackbarMessage(loaderResult.error); setShowSnackbar(true); } }, [loaderResult]); return ( <div> {/* 页面原有内容 */} {loaderResult.data && <div>数据展示:{JSON.stringify(loaderResult.data)}</div>} {/* 错误提示组件 */} <Snackbar open={showSnackbar} message={snackbarMessage} onClose={() => setShowSnackbar(false)} /> </div> ); }
3. 全局提示的优化方案(可选)
如果多个组件都需要处理这类错误,可以用React Context封装提示逻辑,减少重复代码:
// SnackbarContext.js import { createContext, useContext, useState } from 'react'; const SnackbarContext = createContext(); export function SnackbarProvider({ children }) { const [snackbarState, setSnackbarState] = useState({ open: false, message: '' }); const showSnackbar = (message) => { setSnackbarState({ open: true, message }); }; const hideSnackbar = () => { setSnackbarState({ open: false, message: '' }); }; return ( <SnackbarContext.Provider value={{ ...snackbarState, showSnackbar, hideSnackbar }}> {children} <Snackbar open={snackbarState.open} message={snackbarState.message} onClose={hideSnackbar} /> </SnackbarContext.Provider> ); } export function useSnackbar() { return useContext(SnackbarContext); }
在Root组件中使用:
function Root() { const loaderResult = useLoaderData(); const { showSnackbar } = useSnackbar(); useEffect(() => { if (loaderResult.error) { showSnackbar(loaderResult.error); } }, [loaderResult, showSnackbar]); return ( <div> {/* 页面内容 */} </div> ); }
4. 保留errorElement处理致命错误
原来的errorElement继续保留,专门处理需要跳转错误页的致命错误:
const router = createBrowserRouter([ { path: "/", element: <Root />, errorElement: <ErrorPage />, // 仅处理致命错误 loader: rootLoader, children: [ { path: "contacts/:contactId", element: <Contact />, // 子路由同理,loader返回错误信息,组件内部处理 loader: async ({ params }) => { try { const res = await fetch(`/api/contacts/${params.contactId}`); if (!res.ok) { const errorData = await res.json(); if (res.status === 422) { return { error: errorData.message }; } throw new Response(JSON.stringify(errorData), { status: res.status }); } return { data: await res.json() }; } catch (err) { throw err; } } }, ], }, ]);
关键注意点
- 严格区分致命错误和非致命错误:只有需要跳转错误页的情况才抛出异常,其余情况返回错误信息
- 组件内部通过
useLoaderData获取结果,配合状态管理展示提示 - 全局提示用Context可以提升代码复用性,避免重复逻辑
内容的提问来源于stack exchange,提问作者confused
相关产品推荐
相关产品推荐

