The useParams hook returns an empty object when called in a component that is not rendered within a route containing URL parameters. This typically happens when the route path does not include dynamic segments like :id, or when the component is used outside the routing context.
The useParams hook in React Router is designed to extract dynamic URL parameters from the current route. When useParams returns an empty object or undefined values, it means the component is either not rendered as part of a route with parameter definitions, or the route path doesn't contain the expected dynamic segments. React Router matches route paths to URLs and extracts parameter values based on the colon-prefixed segments in the path (e.g., /users/:userId). If your route path is static (like /about) or the component using useParams isn't connected to a parameterized route, the hook will return an empty object. This error commonly occurs when developers expect parameters to be available but haven't properly configured the route structure, or when they're using useParams in a component that's rendered outside the routing hierarchy.
Check that your route definition includes the colon-prefixed parameter syntax:
// ❌ Incorrect - no parameters defined
<Route path="/users" element={<UserProfile />} />
// ✅ Correct - parameter defined with :userId
<Route path="/users/:userId" element={<UserProfile />} />The parameter name after the colon (e.g., :userId) becomes the key in the object returned by useParams.
The useParams hook must be called within a component that is rendered by a Route:
// ❌ Incorrect - useParams in parent component
function App() {
const { userId } = useParams(); // Returns empty object
return (
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
);
}
// ✅ Correct - useParams in routed component
function UserProfile() {
const { userId } = useParams(); // Correctly accesses userId
return <div>User ID: {userId}</div>;
}
function App() {
return (
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
);
}Ensure you're importing useParams from the correct package:
// ❌ Incorrect import
import { useParams } from 'react-router';
// ✅ Correct import for React Router v6
import { useParams } from 'react-router-dom';In React Router v6, all hooks should be imported from react-router-dom, not react-router.
Confirm your component tree includes a Router wrapper:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// ✅ Correct - Component inside Router
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
</BrowserRouter>
);
}Without BrowserRouter (or another Router component), useParams won't work.
Since TypeScript types parameters as possibly undefined, add defensive checks:
import { useParams, Navigate } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams<{ userId: string }>();
// Handle missing parameter
if (!userId) {
return <Navigate to="/users" replace />;
}
return <div>User ID: {userId}</div>;
}This prevents runtime errors when parameters are unexpectedly missing.
When testing, wrap components with MemoryRouter and include the parameter:
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import UserProfile from './UserProfile';
test('displays user ID from params', () => {
render(
<MemoryRouter initialEntries={['/users/123']}>
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText(/User ID: 123/i)).toBeInTheDocument();
});The initialEntries prop simulates navigation to the parameterized route.
Nested Routes and Parameters
When working with nested routes, parameters from parent routes are available to child components:
<Route path="/users/:userId" element={<UserLayout />}>
<Route path="posts/:postId" element={<PostDetail />} />
</Route>
function PostDetail() {
const { userId, postId } = useParams(); // Both available
}Query Parameters vs Route Parameters
Note that useParams is specifically for route path parameters (e.g., /users/:id), not query strings. For query parameters like ?filter=active&page=2, use the useSearchParams hook instead:
import { useSearchParams } from 'react-router-dom';
function UserList() {
const [searchParams] = useSearchParams();
const filter = searchParams.get('filter'); // "active"
const page = searchParams.get('page'); // "2"
}Optional Parameters
React Router v6 doesn't support optional parameters directly. To handle optional segments, create separate routes:
<Routes>
<Route path="/users" element={<UserList />} />
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>Wildcard Parameters
For catch-all routes, use the asterisk syntax:
<Route path="/docs/*" element={<Documentation />} />
function Documentation() {
const { '*': splat } = useParams(); // Captures remaining path
}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