This error occurs when you try to use the useSearchParams hook from React Router outside of a component wrapped by a Router provider. The hook requires the routing context to access URL search parameters.
The `useSearchParams` hook is part of React Router and relies on the Router context to function. When you call this hook in a component that isn't rendered within a `<BrowserRouter>`, `<RouterProvider>`, or similar Router component, React Router cannot access the routing information it needs. This is a common issue that happens when: - Components using the hook are rendered outside your router setup - Testing components in isolation without proper Router wrapper - Calling the hook at the root level before the Router is initialized - Rendering components that use routing hooks in non-routed parts of your application The error is React Router's way of enforcing that routing hooks can only be used within the proper routing context. This ensures the hook can safely access and modify URL search parameters.
First, check that your application is properly wrapped with a Router provider at the root level. Open your main entry point (usually App.tsx or main.tsx) and ensure it looks like this:
// App.tsx or main.tsx
import { BrowserRouter } from 'react-router-dom';
import { Routes, Route } from 'react-router-dom';
import MyComponent from './components/MyComponent';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<MyComponent />} />
{/* other routes */}
</Routes>
</BrowserRouter>
);
}
export default App;If you're using React Router v6.4+ with data routers, use RouterProvider instead:
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import MyComponent from './components/MyComponent';
const router = createBrowserRouter([
{
path: '/',
element: <MyComponent />,
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;Ensure that any component using useSearchParams is rendered as part of your route tree. The hook must be called within components that are descendants of the Router provider.
Incorrect (component outside router):
// main.tsx
import { BrowserRouter } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom'; // ❌ Won't work here
function App() {
const [searchParams] = useSearchParams(); // ❌ Error!
return (
<BrowserRouter>
{/* routes */}
</BrowserRouter>
);
}Correct (component inside router):
// MyComponent.tsx
import { useSearchParams } from 'react-router-dom';
function MyComponent() {
const [searchParams, setSearchParams] = useSearchParams(); // ✅ Works
const query = searchParams.get('q');
return <div>Search query: {query}</div>;
}
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import MyComponent from './MyComponent';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<MyComponent />} />
</Routes>
</BrowserRouter>
);
}When testing components that use useSearchParams, wrap them in a Router provider. Use MemoryRouter for tests since it doesn't require a browser environment:
// MyComponent.test.tsx
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('should render without errors', () => {
render(
<MemoryRouter initialEntries={['/?q=test']}>
<MyComponent />
</MemoryRouter>
);
expect(screen.getByText(/Search query: test/i)).toBeInTheDocument();
});
});For more complex test scenarios with routes:
import { render } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import MyComponent from './MyComponent';
test('renders with search params', () => {
render(
<MemoryRouter initialEntries={['/?filter=active&page=2']}>
<Routes>
<Route path="/" element={<MyComponent />} />
</Routes>
</MemoryRouter>
);
// assertions...
});Ensure you don't have nested BrowserRouter components or conflicting router setups. You should only have ONE router provider at the root of your application.
Incorrect (nested routers):
function App() {
return (
<BrowserRouter>
<Layout>
<BrowserRouter> {/* ❌ Don't nest routers */}
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
</Layout>
</BrowserRouter>
);
}Correct (single router):
function App() {
return (
<BrowserRouter>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</Layout>
</BrowserRouter>
);
}If you need nested routes, use <Outlet /> instead of nesting routers.
If you truly need to access search parameters outside a routed component (like in a utility function), use the native URLSearchParams API directly:
// utils/searchParams.ts
export function getSearchParam(key: string): string | null {
if (typeof window === 'undefined') return null;
const searchParams = new URLSearchParams(window.location.search);
return searchParams.get(key);
}
export function getAllSearchParams(): Record<string, string> {
if (typeof window === 'undefined') return {};
const searchParams = new URLSearchParams(window.location.search);
const params: Record<string, string> = {};
searchParams.forEach((value, key) => {
params[key] = value;
});
return params;
}However, this approach won't trigger re-renders when search params change, so prefer using the hook within routed components whenever possible.
Understanding React Router Context Architecture
React Router uses React's Context API to provide routing state throughout your component tree. Hooks like useSearchParams, useNavigate, useLocation, and useParams all rely on this context. When you call these hooks, React looks up the component tree for the nearest Router context provider.
React Router v6.4+ Data Routers
If you're using the newer data router APIs (createBrowserRouter, RouterProvider), the context is provided by RouterProvider. Components rendered through the router configuration have access to all routing hooks:
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
children: [
{
path: 'search',
element: <SearchPage />, // Can use useSearchParams
},
],
},
]);Server-Side Rendering Considerations
When using SSR frameworks like Remix or Next.js with React Router, be aware that useSearchParams behavior may differ. In Remix, for example, you might use useSearchParams from @remix-run/react instead. Check your framework's documentation for the correct import.
Custom Router Implementations
If you're building a custom router or integrating with other routing libraries, you'll need to provide the Router context yourself. This is rarely necessary but can be done using React Router's lower-level APIs like Router component with a custom history object.
Migration from Class Components
Class components cannot use hooks directly. If you need search params in a class component, either:
1. Convert to a function component
2. Create a HOC that wraps the component and passes search params as props
3. Use withRouter (deprecated in v6) or create a custom wrapper component
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite