简体   繁体   中英

Dynamic React Router and component not possible?

I'm trying to build a dynamic router with React. The idea is that the Routes are created from the data (object) received from the backend:

Menu Object:

items: [
    {
      name: "dashboard",
      icon: "dashboard",
      placeholder: "Dashboard",
      path: "/",
      page: "Dashboard",
      exact: true,
    },
    {
      name: "suppliers",
      icon: "suppliers",
      placeholder: "Suppliers",
      path: "/suppliers",
      page: "Suppliers",
      exact: true,
    }
]

The routes hook:

export const RouteHook = () => {
  // Dispatch to fetch the menu object from the backend
  const dispatch = useDispatch();
  dispatch(setMenu());

  // Select the menu and items from Redux
  const { items } = useSelector(getMainMenu);

  // useState to set the routes inside
  const [menuItems, setMenuItems] = useState([]);

  
  const { pathname } = useLocation();

  useEffect(() => {
    // Loop trough the menu Items
    for (const item of items) {
      const { page, path, name, exact } = item;

      // Import dynamically the route components
      import(`../pages/${name}/${page}`).then((result) => {
        // Set the routes inside the useState
        setMenuItems(
          <Route exact={exact} path={path} component={result[page]} />
        );
      });
    }
     // Check if pathname has changed to update the useEffect
  }, [pathname]);

  return (
    <Switch>
      {/* Set the routes inside the switch */}
      {menuItems}
    </Switch>
  );
};

Now here's the problem. Not all the components load. Usually the last component loads and when clicking on a diffrent route the component won't change. Except if you got to the page and refresh (F5).

What am I missing here? Is it possible to create full dynamic routes & components in react?

I'm not sure 100% what's going on here, but here's a problem I see:

const [menuItems, setMenuItems] = useState([]);

You're saying that menuItems is an array of something. But then:

import(`../pages/${name}/${page}`).then((result) => {
  // Set the routes inside the useState
  setMenuItems(
    <Route exact={exact} path={path} component={result[page]} />
  );
});

On every iteration you are setting the menu items to be a singular Route component. Probably what you're thinking youre doing is

const routes = items.map(item => {

  const { page, path, name, exact } = item;

  return import(`../pages/${name}/${page}`).then((result) => {
    <Route exact={exact} path={path} component={result[page]} />
  });

})

setMenuItems(routes)

But this makes no sense, because your map statement is returning a Promise.then function. I'm not entirely sure why you're dynamically importing the components here. You're better off doing a simple route mapping:

const routes = items.map(item => {

  const { page, path, name, exact } = item;

  return <Route exact={exact} path={path} component={components[page]} />

})

setMenuItems(routes)

Where components is an object whose keys are the values of page and whose values are actual components, ie:

const components = {
  Suppliers: RenderSuppliers,
  Dashboard: RenderDashboard
}

If you want these components lazy-loaded, use react suspense:

const Suppliers = React.lazy(() => import("./Suppliers"))
const Dashboard = React.lazy(() => import("./Dashboard"))

const components = {
  Suppliers,
  Dashboard,
}


const routes = items.map(item => {
  const { page, path, name, exact } = item;

  return (
    <Suspense fallback={<SomeFallbackComponent />}>
      <Route
        exact={exact}
        path={path}
        component={components[page]}
      />
    </Suspense>
  )

})

setMenuItems(routes)

This is just a quick review of what may be going wrong with your code, without a reproducible example, its hard to say exactly.

Seth has some great suggestions, but here's how you can clean this up while still using dynamic imports.

Hopefully you can see that you are calling setMenuItems with a single Route component instead of all of them. Each time that you setMenuItems you are overriding the previous result and that's why only the last Route actually works -- it's the only one that exists!

Your useEffect depends on the pathname which seems like you are trying to do the routing yourself. Since you are using react-router-dom you would include all of the Route components in your Switch and let the router handle the routing.

So you don't actually need any state here.

You can use the React.lazy component import helper inside of the Route . You need a Suspense provider around the whole block in order to use lazy imports.

I don't like that you use two variables in the path for a component ../pages/${name}/${page} . Why not export the component from the ./index.js of the folder?

export const Routes = () => {
  // Dispatch to fetch the menu object from the backend
  const dispatch = useDispatch();
  dispatch(setMenu());

  // Select the menu and items from Redux
  const items = useSelector((state) => state.routes.items);

  return (
    <Suspense fallback={() => <div>Loading...</div>}>
      <Switch>
        {items.map(({ exact, path, name }) => (
          <Route
            key={name}
            exact={exact}
            path={path}
            component={React.lazy(() => import(`../pages/${name}`))}
          />
        ))}
      </Switch>
    </Suspense>
  );
};

It works!

Code Sandbox Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM