reactjsreact-router-dom

How to safely use useNavigate in a reusable React component library


I'm building a reusable dashboard package that I publish to npmjs. Consumers can provide dynamic menu items and top bar configuration via props, like this:

<Dashboard
  menuItems={[
  {
      label: 'Home',
      icon: 'home',
      action: ({ navigate }) => navigate('/')
  }
  ]}
  topBarConfig={...}
/>

To enable navigation in these actions, I use useNavigate() inside my dashboard's internal LeftMenu component:



    import { useNavigate } from 'react-router-dom';
    
    const LeftMenu = ({ menuItems }) => {
      const navigate = useNavigate(); //  {
        item.action({ navigate, closeMenu: () => {/* close logic */} });
      };
    
      ...
    };

This works perfectly fine when used locally (i.e. consuming the dashboard component directly inside the same project), but when I install it from npm, and use it like this:

<pre><code>
import AuthProvider, { useAuth } from "./Providers/AuthProvider";
import Login from './Login';
import ProtectedRoutes from "./ProtectedRoute";
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
import { Dashboard, ThemeProvider, SearchWidget, UserDropdown } from 'leximo-dashboard';
import 'leximo-dashboard/dist/assets/style.css';

export default function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <Router>
          <AppContent />
        </Router>
      </ThemeProvider>
    </AuthProvider>
  );
}

function AppContent() {
  const { user } = useAuth();

  const menuItems = [
    {
      label: 'Home',
      icon: 'home',
      action: ({ navigate }) => navigate('/')
    }
  ];

  const topBarConfig = {
    leftContent: null,
    centerContent: <SearchWidget placeholder="Find anything..." onSearch={(term) => console.log(term)} />,
    rightContent: (
      <UserDropdown
        items={[
          { label: 'Profile', icon: 'user' },
          { label: 'Notifications', icon: 'bell' },
          { label: 'Logout', icon: 'sign-out-alt' }
        ]}
      />
    ),
    showThemeToggle: true
  };

  return (
    <Routes>
      <Route element={<ProtectedRoutes />}>
        <Route path="/" element={<Dashboard menuItems={menuItems} topBarConfig={topBarConfig} />} />
      </Route>
      <Route path="/login" element={<Login />} />
      <Route path="*" element={<Navigate to="/" replace />} />
    </Routes>
  );
}

then, I get the following runtime error:

Uncaught Error: useNavigate() may be used only in the context of a <Router> component

Solution

  • The error means that leximo-dashboard uses react-router-dom copy that differs from the one that's used in the project.

    There can be several causes for this. The actual problem is that leximo-dashboard bundles react-router-dom package, this can be easily seen by looking at the size of the bundle. This should be avoided, if the package is bundled with Vite, react* packages should be added to build.rollupOptions.external.

    Additional things to consider: