useReactRouterEnableConcurrentNavigation (React Router v6)

Exports Size
loading...
Gzip Size
loading...
Brotli Size
Source Code
View on GitHub
Docs
Edit this page

Improve React Router v6's navigation experience with <Suspense />, React.lazy(), and useReactRouterEnableConcurrentNavigation() using the power of React Transition and React Concurrent Rendering. Addresses Dan Abramov's feature request Remix issue #5763: Integrating Remix Router with React Transitions (opens in a new tab).

Usage

Below is an example of how to use <Suspense />, React.lazy(), and useReactRouterEnableConcurrentNavigation() to bring lazyload and smooth transition to your React Router v6 application:

src/index.tsx
// The entry point of your app
 
import { StrictMode } from 'react';
import { RouterProvider } from 'react-router-dom';
 
import { router } from './router';
 
function App() {
  return (
    <StrictMode>
      <RouterProvider router={router} />
    </StrictMode>
  );
}
 
const el = document.getElementById('app');
if (el) {
  createRoot(el).render(<App />);
}
src/router/index.tsx
// The router declaration
 
import { lazy, memo } from 'react';
import { createBrowserRouter, useRouteError, isRouteErrorResponse } from 'react-router-dom';
 
import { RootLayout } from '@/layouts/root-layout';
import { DashboardLayout } from '@/layouts/sub-layout';
import { ProfileLayout } from '@/layouts/sub-layout';
 
const RouterErrorBoundary = memo(() => {
  const error = useRouteError();
  if (isRouteErrorResponse(error)) {
    if (error.status === 404) {
      return <NotFoundPage />;
    }
  }
  return <GlobalErrorPage />;
});
 
// Use `React.lazy()` for code splitting and lazy loading
const Homepage = lazy(() => import(/* webpackPrefetch: true */ '@/pages/home'));
const Dashboard = lazy(() => import(/* webpackPrefetch: true */ '@/pages/dashboard'));
const Profile = lazy(() => import(/* webpackPrefetch: true */ '@/pages/profile'));
 
export const router = createBrowserRouter([
  {
    element: <RootLayout />,
    errorElement: <RouterErrorBoundary />,
    children: [{
      path: 'dashboard',
      element: <DashboardLayout />,
      children: [
        {
          index: true,
          element: <Dashboard />
        },
        // A common nested route pattern
        {
          path: 'profile',
          element: <ProfileLayout />,
          children: [
            {
              index: true,
              element: <Profile />
            }
          ]
        }
      ]
    }]
  }
]);

In your root layout component (<RootLayout />), invoke useReactRouterEnableConcurrentNavigation:

src/layouts/root-layout.tsx
import { Suspense } from 'react';
import { Outlet } from 'react-router-dom';
 
import { useReactRouterEnableConcurrentNavigation } from `foxact/use-react-router-enable-concurrent-navigation`;
 
export const RootLayout = () => {
  useReactRouterEnableConcurrentNavigation();
 
  return (
    <SomeFancyLayout>
      <Suspense fallback={<Skeleton />}>
        {/**
          * Wraps your <Outlet /> with <Suspense /> to for lazy loading
          * and concurrent rendering
          */}
        <Outlet />
      </Suspense>
    </SomeFancyLayout>
  )
};

If you prefer the Provider component approach, foxact also gets you covered:

src/layouts/root-layout.tsx
import { Suspense } from 'react';
import { Outlet } from 'react-router-dom';
 
import { ReactRouterConcurrentNavigationProvider } from `foxact/use-react-router-enable-concurrent-navigation`;
 
export const RootLayout = () => (
  <ReactRouterConcurrentNavigationProvider>
    <SomeFancyLayout>
      <Suspense fallback={<Skeleton />}>
        <Outlet />
      </Suspense>
    </SomeFancyLayout>
  </ReactRouterConcurrentNavigationProvider>
);

Note that you should choose one of useReactRouterEnableConcurrentNavigation and <ReactRouterConcurrentNavigationProvider /> in your app, not both. You only need to invoke it once in your app, and you should invoke it under the <RouterProvider /> (to have access to NavigationContext). It is recommended to put it in your root layout component.

Normally, if your app is built with React Router, <Suspense />, and React.lazy(), the Suspense fallback will always be shown during the navigation, resulting in a bad user experience. With useReactRouterEnableConcurrentNavigation or <ReactRouterConcurrentNavigationProvider />, Suspense fallback won't re-appear during the navigation, resulting in a smooth user experience.