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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 15:17:02