ant-design / ant-design-pro

๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป Use Ant Design like a Pro!
https://pro.ant.design
MIT License
36.57k stars 8.15k forks source link

๐Ÿง [ ๐Ÿ› ] setPathName not functioning correctly on npm run dev environment #11244

Open yogithesymbian opened 6 months ago

yogithesymbian commented 6 months ago

๐Ÿง ้—ฎ้ข˜ๆ่ฟฐ | Problem description

In development mode (npm run dev), I am encountering an issue with menu navigation. Specifically:

However, in production mode (npm run build and npm run start), all menus work as expected without any issues.

the menu url display look like these
menu1 `http://localhost:3000/dashboard/antd/manifest`
menu2 `http://localhost:3000/dashboard/antd/manifest?layout=mix&splitMenus=true&navTheme=realDark`
menu3 `http://localhost:3000/dashboard/propage`

๐Ÿ’ป ็คบไพ‹ไปฃ็  | Sample code

Here is a snippet of my code that renders the menu items:

menuItemRender={(item, dom) => (
  <Link legacyBehavior href={item.path || "/dashboard"}>
    <a
      onClick={async (e) => {
        e.preventDefault();
        await handleMenuClick(item.path || "/dashboard");
      }}
    >
      {dom}
    </a>
  </Link>
)}
{...settings}

And here is the handler function:

const handleMenuClick = async (path: string) => {
  await router.push(path);
  setPathname(path); // Doesn't work in `npm run dev`
  // setTimeout(() => {  // This works if set to 800 milliseconds or more
  //   setPathname(path);
  // }, 800);
};

"next": "^14.2.3",

๐Ÿš‘ ๅ…ถไป–ไฟกๆฏ | Other information

In development mode, I have to use a setTimeout with at least 800 milliseconds to make the setPathname function work as expected. This issue does not occur in production mode.

Fettes commented 5 months ago

From my perspective, I have trouble understanding your problem. The first thing is, you don't need to use async/await func to do the navigation. if your problem is:

router.push(path) // not work on menu2 in dev mode

you should check your router. I think you are using React. and it's better to use history or useNavigate hook.

history.push('/dashboard/antd/manifest?layout=mix&splitMenus=true&navTheme=realDark');

// or

history.push({
    pathname: '/dashboard/antd/manifest',
    search: '?layout=mix&splitMenus=true&navTheme=realDark',
  }
);

And what make me confused is

setPathname(path); // Doesn't work in `npm run dev` // ?? this line do nothing to your navigation

corrent me if I am wrong.

yogithesymbian commented 5 months ago

@Fettes

setPathname(path); // Doesn't work in `npm run dev` // ?? this line do nothing to your navigation

The changes for themes on the SettingDrawer from antd pro layouts need to call setPathname to correctly update the current location (navigation on the sidebar, etc.). It should also have a callback/return data and automatically update the URL with ?layout=mix&splitMenus=true&navTheme=realDark.

i use this code https://procomponents.ant.design/components/layout?tab=api#%E4%BB%A3%E7%A0%81%E6%BC%94%E7%A4%BA its a demo pro layout

Fettes commented 5 months ago

@yogithesymbian
Now I see. Maybe the problem is blame to the Strict Mode of React in dev env.

It seems that after a route change in the dev env, the state update of setPathname might not reflect immediately, but using setTimeout to defer the update to later in the browser's event loop seems to have the desired effect.

The issue may be due to a race condition between React's state updates and the routing navigation. After router.push(path)is called, the React state update might not have processed yet, and the routing navigation might trigger the unmounting and remounting of the component. The new instance of the component might mount before the previous state update is complete, which could then lead to the state update being missed or conflicting with the lifecycle of the components.

In this scenario, your router.push is an asynchronous operation, it may result in the component being re-rendered before the new route's page is rendered, then the setPathname call updates the component state and it does't work.

So you use setTimeout with a delay (800 milliseconds) circumvents this issue because it effectively waits for all existing work in the event loop, including route changes and page rendering, to complete. This ensures that by the time setPathName is called.

To address this issue, you might try the following approaches:

Route Event Listener: Listen for the route change completion event, and then update the state in the event callback.

router.events.on('routeChangeComplete', handleRouteChange);

function handleRouteChange(url) {
  setPathfinder(url);
}

// [...]

return () => {
  router.events.off('routeChangeComplete', handleRouteChange);
};

Coordinate State with Route Changes: Ensure state updates are coordinated with route changes using useEffect or other React hooks, based on the route path.

useEffect(() => {
  setPathname(router.currentRouter.path);
}, [router.currentRouter.path]);

Not sure whether the methods mentioned will work or not. I have done my utmost. :)