I've just started to do lots of asynchronous child route loading. Since AFAIK Router doesn't render anything until all of the getChildRoutes calls have called back, the page might be blank/static for a long time during initial load on a slow internet connection, or when loading a code-split bundle that handles certain subroutes.
Obviously, I could dispatch some redux actions in my getChildRoutes that trigger a loading message that lives on top of my <Router> in the hierarchy. However, if I could just put a loadingChildRoutesComponent on each <Route> with getChildRoutes, it would have the following advantages:
getChildRoutes<Route>'s componentWhat do you guys think? The biggest stumbling block is that the match function would likely need to be changed to support this so that it could return the already-loaded routes/components/loading component initially and return the fully loaded routes/components later.
We want to do something like this, but it's tricky, because we don't want to force users to opt into an extra re-render if they're not using this feature.
Wondering if this would be best done with some combination of history/router middleware.
Hmmm, how do you mean it would force an extra re-render?
That's a good point about middleware, do you mean by using the createElement prop or are there other plans for middleware?
In any case I realized I can work a prop like this into some tools I made that recursively clone the <Route>s to support extra props
I mean the applyRouterMiddleware stuff from 2.3.0
not in the docs?
Okay, I found react-router-apply-middleware and wrote some middleware for other purposes yesterday. I also came up with a solution that doesn't involve using a loadingChildRoutesComponent.
I know middleware is great for replacing getComponent, but that's because getComponent is often inferior to passing in a component that async-loads another component. getChildRoutes is fundamentally different because the route component (or one provided by middleware) can't determine what child routes it has (though I would love to be able to write HoC routes that render a <Route> with the desired childRoutes, and any props except path).
To solve this issue with middleware, middleware would need to be able to clone <Route>s themselves (which I don't see any evidence it can do, at least not yet), because it would need to replace the getChildRoutes with one that:
<Route path="*" component={LoadingComponent}> while the original getChildRoutes is loadinggetChildRoutes instead, orgetChildRoutes errored, return <Route path="*" component={ErrorComponent}>My solution was to do the above 3 things in my getChildRoutes, which actually wasn't too bad. But it had one slightly hacky aspect: to get the transition manager to call my getChildRoutes again when it had errored/loaded successfully, I had to wrap the history with one that also subscribes to my redux store and fires off new locations (same data, different object instance) when the relevant redux state changes, so that the transition manager reruns match.
I'm not sure if supporting a loadingChildRoutesComponent and running an async getChildRoutes in the background would be any better though, because the complexity would just get pushed into getChildRoutes -- it would have to subscribe to the redux store to listen for when its data is available.
applyRouterMiddleware is in this library now BTW.
@jedwards1211
To solve this issue with middleware, middleware would need to be able to clone
s themselves (which I don't see any evidence it can do, at least not yet)
There is React.cloneElement to clone elements and replace props. In your case getChildRoutes.
@hoschi obviously, what I mean is I don't think middleware can call React.cloneElement on the <Route>s and pass them back to the router, so it can't affect the getChildRoutes the router actually calls. Or am I missing something?
Mhh you are right! The middleware receives only the component property of a <Route> and not the <Route> element itself.
@hoschi right, so if the Router passed its <Route>s through some kind of transformRoute function in the middleware, then it would be possible to support all kinds of custom props on the <Route>s.
(though it is already possible to do that with some crazy recursive cloning; for instance I do it in my redux-plugins-immutable-react-router library)
the problem may be resolved with component Lifecycle - call componentWillUnmount immediately before starting loading routes, not just when a new component loaded
@budarin I don't understand what you mean
@jedwards1211 I've asked some time ago about the same issue, and got advised to use match() helper. In this case you can put the default html into the app container and it will be replaced only after the route resolved. https://github.com/reactjs/react-router/blob/master/docs/API.md#match-routes-location-history-options--cb
Have you considered this approach?
With react-router-redux you can do something like this:
@connect(state => {
currentRouteKey: state.routing.locationBeforeTransitions.key
})
export default Root function({
currentRouteKey,
location: {
key
},
children
}) {
const isLoading = key != currentRouteKey;
return isLoading ? <Loader/> : children;
}
Value in state.routing will update immediately on location change
Value in this.props.location will update after success async load
EDIT: Same thing can be done with history.getCurrentLocation() in v3 history
@umidbekkarimov Cool, thanks for the tip!
v4's new declarative structure means async stuff is handled externally and there is no getComponent equivalent. So, this is now a moot point :)
Most helpful comment
With react-router-redux you can do something like this:
Value in
state.routingwill update immediately on location changeValue in
this.props.locationwill update after success async loadEDIT: Same thing can be done with
history.getCurrentLocation()in v3 history