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!
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.