React Router v6中navigate()改路径但不渲染视图问题求助
导航不渲染问题:删除条目后URL变更但页面未更新
问题概述
我要实现以下逻辑:
- 用户点击
Delete按钮时,执行onDelete并分发Redux Thunk动作; - 条目删除完成后,导航至
/home页面。
目前遇到的问题:在dispatch的promise的then()中添加导航回调后,点击删除按钮,条目成功删除,URL变为/home,但页面并未渲染/home的内容。尝试过以下方法均无效:
- 给
navigate添加replace: true选项; - 用
async await替代then(); - 使用相对路径
../../home。
移除dispatch(deleteTrip())后,navigate('/home')可正常工作,说明分发Redux动作干扰了导航流程。
相关代码
App.tsx
function App() { // ... // Router const router = createBrowserRouter( createRoutesFromElements( <> <Route index path="/" element={<RootPage />} errorElement={<ErrorPage />} /> <Route path="/login" element={<LoginPage />} /> <Route path="*" element={<NotFound />} /> <Route element={<PrivateRoutes />}> <Route path="/profile/edit" element={<ProfileEditPage />} loader={profileLoader} /> <Route path="/home" element={<HomePage />} loader={triplistLoader} /> <Route path="/create" element={<TripEditPage />} /> <Route element={<BottomNav />}> <Route path="/trip/:tripId" element={<MainPage />} /> </Route> </Route> {/* Not Found Page */} </> ), { basename: process.env.PUBLIC_URL } ); return <RouterProvider router={router} />; } export default App;
BannerTopNav.tsx(属于/trip/:tripId页面)
// ... function BannerTopNav() { const authCtx = useAuthState(); const dispatch = useAppDispatch(); const [viewMenu, setViewMenu] = useState<boolean>(false); const dropDownRef = useRef<HTMLDivElement>(null); const navigate = useNavigate(); const params = useParams(); const tripId = params.tripId; const onDelete = () => { if (authCtx.state === "loaded" && authCtx.user && tripId) { dispatch(deleteTrip({ uid: authCtx.user.uid, id: tripId })).then(() => { navigate("/home", { replace: true }); }); } }; // ... return ( // ... {viewMenu && ( <DropdownMenu menuArr={[ { label: "Delete", onClick: onDelete }, ]} />)} } export default BannerTopNav;
store/TripReducer.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import TripAPI from "../utils/trip/apis"; import { PURGE } from "redux-persist"; export interface ITripData { title: string; user_id: string; image?: string; start_date: number; end_date: number; created_at: number; // timestamp updated_at: number; // timestamp } export interface ITrip { id: string; data: ITripData; } // [{id: ..., data: ...}, ...] export interface ITriplist extends Array<ITrip> {} export interface ITriplistState { status: "loading" | "loaded" | "error"; state: ITriplist | undefined; } const initialState: ITriplistState = { status: "loading", state: undefined, }; export const getTriplist = createAsyncThunk( "GET_TRIPLIST", async (uid: string, thunkAPI) => { return TripAPI.get(uid); } ); export const addTrip = createAsyncThunk( "CREATE_TRIP", async (args: { uid: string; data: ITripData }, thunkAPI) => { return TripAPI.add(args.uid, args.data); } ); export const updateTrip = createAsyncThunk( "UPDATE_TRIP", async (args: { uid: string; data: ITrip; id: string }, thunkAPI) => { // update } ); export const deleteTrip = createAsyncThunk( "DELETE_TRIP", async (args: { uid: string; id: string }, thunkAPI) => { return TripAPI.delete(args.uid, args.id); } ); export const triplistSlice = createSlice({ name: "triplist", initialState, reducers: {}, extraReducers: (builder) => { builder.addCase(getTriplist.fulfilled, (state, action) => { state.status = "loaded"; state.state = action.payload; }); builder.addCase(addTrip.fulfilled, (state, action) => { const { id, data } = action.payload; state.state?.push({ id, data }); }); builder.addCase(deleteTrip.fulfilled, (state, action) => { const deleted_id = action.payload; state.state = state.state?.filter((trip) => trip.id !== deleted_id); }); builder.addCase(PURGE, () => initialState); }, });
store/index.ts
import { combineReducers, configureStore } from "@reduxjs/toolkit"; import { profileSlice } from "./profileReducer"; import { persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; import { triplistSlice } from "./triplistReducer"; // 리듀서 설정 const reducers = combineReducers({ profileReducer: profileSlice.reducer, triplistReducer: triplistSlice.reducer, }); // redux-persist를 통한 상태 로컬 저장 설정 const persistConfig = { key: "root", storage, // 로컬스토리지에 저장 // whitelist: 지정된 reducer를 저장 // blacklist: 지정된 reducer를 저장에서 제외 }; const persistedReducer = persistReducer(persistConfig, reducers); // redux store 설정 export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;
排查与解决方法
1. 检查HomePage的Loader是否正常
/home路由配置了loader={triplistLoader},如果loader执行出错或卡住,会导致页面无法渲染。可以暂时注释掉loader配置,测试导航后是否能显示HomePage:
// 修改App.tsx中的/home路由 <Route path="/home" element={<HomePage />} /> {/* 移除loader */}
如果注释后导航正常,说明loader存在问题,需要排查loader的逻辑(比如是否正确获取用户ID、API调用是否出错等)。
2. 确保dispatch的Thunk正确Resolve
确认TripAPI.delete返回的是一个已完成的Promise,可在Thunk中添加日志验证:
export const deleteTrip = createAsyncThunk( "DELETE_TRIP", async (args: { uid: string; id: string }, thunkAPI) => { try { const result = await TripAPI.delete(args.uid, args.id); console.log("删除接口返回:", result); return result; } catch (err) { console.error("删除失败:", err); throw err; // 抛出错误让组件捕获 } } );
3. 用useCallback包裹onDelete函数
避免组件重渲染导致函数引用变化,确保导航动作稳定执行:
import { useCallback } from 'react'; // ... const onDelete = useCallback(async () => { if (authCtx.state === "loaded" && authCtx.user && tripId) { try { await dispatch(deleteTrip({ uid: authCtx.user.uid, id: tripId })); console.log("Redux动作完成,开始导航"); navigate("/home", { replace: true }); } catch (err) { console.error("删除流程出错:", err); } } }, [authCtx, dispatch, tripId, navigate]);
4. 排查Redux-persist的影响
Redux-persist的异步持久化操作可能导致组件额外重渲染,干扰导航。可以暂时移除persist配置测试:
// 修改store/index.ts,直接使用reducers而非persistedReducer export const store = configureStore({ reducer: reducers, // 替换persistedReducer middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), });
如果测试后导航正常,说明persist配置需要调整,比如将triplistReducer加入blacklist,避免持久化该状态:
const persistConfig = { key: "root", storage, blacklist: ["triplistReducer"], // 不持久化triplist状态 };
5. 临时用原生导航测试
如果以上方法都无效,可以尝试用原生浏览器导航替代React Router的navigate,排查是否是React Router的问题:
const onDelete = async () => { if (authCtx.state === "loaded" && authCtx.user && tripId) { await dispatch(deleteTrip({ uid: authCtx.user.uid, id: tripId })); window.location.replace("/home"); // 硬刷新导航 } };
注意:这种方法会刷新页面,仅用于排查,不推荐作为最终解决方案。
内容的提问来源于stack exchange,提问作者minsuhan
相关产品推荐
相关产品推荐

