I'm building the app with hierarchical structure and ability to set any slug url the user wants by the admin panel. For example, if the top page has url news
and the page inside has url new_telescope
, the whole url would be /news/new_telescope/
. I wrote the url controller to handle this, so it really doesn't matter how many levels are there in the url - I get the data of the needed page from the server. So my React Router
wouldn't have predefined urls - I need to take the current url, send it to server, get the data, get the component and render it. Now it looks like this:
function getRoute() {
url.splice(0, 1);
adminRequest(
'get',
constants.SERVER_ADDRESS `/system/url/?url=` JSON.stringify(url) '&version=1',
).then(response => {
const CurrentComponent = lazy(() => import('./' response.data.template));
return <Route path={window.location.pathname} element={<CurrentComponent page={response.data}/> } />
});
}
return (
<>
<Suspense fallback={<div className='preloader'><CircularProgress size='75px' /></div>}>
<Routes>
// trying to get the current url with proper component
{ getRoute() }
<Route path="/admin/" element={<Admin />} />
<Route path="/admin/login/" element={<Login />} />
...
</Routes>
</Suspense>
</>
);
So the problem is with the getRoute
function. As you can see I make a request, get the component name from the DB and use lazy
to import it. In that function I make a new Route
to put it to Routes
. But I get errors No routes matched location
with any url. And of course the component doesn't render. I thought that Suspense
will wait for the first route to appear but it doesn't work that way. How can I make a Router
to wait for a dynamic route?
CodePudding user response:
Issue
- The
getRoute
function doesn't return anything. Sure, the Promise chain started fromadminRequest
returns some JSX, but that resolved value isn't returned by the other function. - React render functions are 100% synchronous functions. You can't call an asynchronous function and expect React to wait for any asynchronous code to resolve to render any content.
Solution
Use a useEffect
hook to issue the side-effect of fetching the route. Unconditionally render the Route
but conditionally render the element
value.
const [data, setData] = useState();
const [CurrentComponent, setCurrentComponent] = useState();
useEffect(() => {
const getRoute = async () => {
url.splice(0, 1);
const response = await adminRequest(
"get",
constants.SERVER_ADDRESS
`/system/url/?url=`
JSON.stringify(url)
"&version=1"
);
const CurrentComponent = lazy(() =>
import("./" response.data.template)
);
setData(response.data);
setCurrentComponent(CurrentComponent);
};
getRoute();
}, [url]);
...
<Suspense fallback={<div className="preloader">Loading...</div>}>
<Routes>
<Route
path={/* whatever the path is */}
element={CurrentComponent ? <CurrentComponent page={data} /> : null}
/>
<Route path="/admin/" element={<Admin />} />
<Route path="/admin/login/" element={<Login />} />
</Routes>
</Suspense>
Demo