Pending route states are similar to a React Suspense fallback
state, or if you're not familiar with Suspense, a loading
state.
React Location is different from other routing libraries. Because it is asynchronous, it has its own "suspense"-like pending state when a new route is being loaded. If a route doesn't have any data requirements or async dependencies, it can be loaded immediately and the pending state isn't ever observed.
However, if a route has data requirements or async dependencies, it will be loaded in the background and the pending state can be observed in a few ways:
Routes can be configured to have a timeout before the route is considered pending. This timeout is essentially the amount of time we are willing to "suspend" and wait on the current route for the next one to load.
Configure a pending timeout in milliseconds with the pendingMs
prop:
const routes = [{path: '/',loader: () => loadHomeData()element: () => <div>Home</div>,pendingElement: async () => <div>Taking a long time...</div>,pendingMs: 1000 * 2, // 2 seconds},]
pendingElement
will be shown until the route is fully loaded.If and when a pending state is shown for a route, it is important to ensure that it doesn't "flicker" or "flash" when the route resolves too quickly after showing it. To fix this, you can specify a minimum amount of time to show the pending element before showing the normal one. This is done by configuring a minimum timeout in milliseconds with the pendingMinMs
property:
const routes = [{path: '/',loader: () => loadHomeData()element: () => <div>Home</div>,pendingElement: async () => <div>Taking a long time...</div>,pendingMs: 1000 * 2, // 2 secondspendingMinMs: 500 // If it's shown, ensure the pending element is rendered for at least 500ms},]
When a route is pending, it might be useful to show a global indicator to the user. This can be done by subscribing to the Router
's pending
state and rendering a global indicator when it is present.
Here is a very contrived example of a global pending navigation indicator:
function Root() {const router = useRouter<LocationGenerics>()return (<div>{!!router.pending ? <Spinner /> : null}<Outlet /></div>)}
Here are some more tips to try!
null
When a transition is started to an asynchronous route, the current route will remain visible while it loads (unless a pending state is shown). While still visible, it is possible to show pending UI elements in the current route that are specific and conditional to the pending location. The two best tools to do this are the useMatchRoute
hook and MatchRoute
component.
Here's an example using the MatchRoute
component with the pending
prop to show a pending UI element when the link to that route matches:
function App() {return (<Link to="dashboard">Dashboard{' '}<MatchRoute to="dashboard" pending><Spinner /></MatchRoute></Link>)}
You can also use the MatchRoute
component with a function as a child:
function App() {return (<Link to="dashboard">Dashboard{' '}<MatchRoute to="dashboard" pending>{(match) => <Spinner show={!!match} />}</MatchRoute></Link>)}
Likewise, the useMatchRoute
hook is an option if you would like a more functional approach:
function App() {const matchRoute = useMatchRoute()return (<Link to="dashboard">Dashboard{' '}{matchRoute({ to: 'dashboard', pending: true }) ? <Spinner /> : null}</Link>)}
The best JavaScript newsletter! Delivered every Monday to over 76,000 devs.