How to Pass Parameters with useNavigate in React-router-dom v6 (TypeScript): Fixing NavigateOptions Type Error
React Router is the de facto standard for handling navigation in React applications. With the release of version 6, React Router introduced useNavigate, a hook that replaces the older useHistory hook for programmatic navigation. While useNavigate simplifies navigation, passing parameters (e.g., user data, IDs) between routes can be tricky—especially when using TypeScript, where type safety is critical.
A common frustration developers face is the "NavigateOptions type error" when trying to pass state or custom parameters via useNavigate. This error occurs because TypeScript cannot infer the type of the state property in NavigateOptions, leading to unknown type warnings or runtime bugs.
In this guide, we’ll demystify useNavigate, explore two methods for passing parameters (query strings and state), and provide a step-by-step solution to fix the NavigateOptions type error in TypeScript. By the end, you’ll confidently pass parameters between routes while maintaining type safety.
Table of Contents#
- Prerequisites
- Understanding
useNavigateandNavigateOptions - Passing Parameters: Query Strings vs. State
- Fixing the NavigateOptions Type Error in TypeScript
- Step-by-Step Implementation Example
- Common Pitfalls and Solutions
- Conclusion
- References
Prerequisites#
Before diving in, ensure you have:
- Basic knowledge of React and React Hooks.
- Familiarity with TypeScript (interfaces, generics, type annotations).
- A React project set up with TypeScript (e.g., using
create-react-appor Vite). react-router-domv6 installed (runnpm install react-router-domoryarn add react-router-dom).- Type definitions for React Router (included by default if using
npm install @types/react-router-dom).
Understanding useNavigate and NavigateOptions#
What is useNavigate?#
The useNavigate hook returns a function that lets you navigate programmatically (e.g., after a button click or form submission). Its basic syntax is:
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();The navigate function takes two arguments:
to: The target route (a string like/profileor aPartialPathobject).options(optional): ANavigateOptionsobject that can include:state: Custom data to pass to the target route (stored in the browser’s history stack).replace: A boolean (true/false) to replace the current history entry instead of pushing a new one.relative: A boolean to define if the route is relative to the current route.
The NavigateOptions Type Error#
The root cause of the "NavigateOptions type error" in TypeScript is the default type of state in NavigateOptions. By default, state is typed as unknown, meaning TypeScript cannot infer its structure. When you try to pass a custom object (e.g., { user: { id: 1, name: "John" } }) as state, TypeScript throws an error like:
Argument of type '{ state: { user: { id: number; name: string; }; } }' is not assignable to parameter of type 'NavigateOptions'.
Types of property 'state' are incompatible.
Type '{ user: { id: number; name: string; }; }' is not assignable to type 'unknown'.To fix this, we need to explicitly define the type of state using TypeScript generics.
Passing Parameters: Query Strings vs. State#
There are two primary ways to pass parameters with useNavigate:
1. Query Strings#
Query strings are visible in the URL (e.g., /profile?userId=1&name=John) and are ideal for:
- Sharing links (the recipient can see/use the parameters).
- Simple, non-sensitive data (e.g., IDs, filters).
Pros: Persists on page refresh, shareable.
Cons: Limited to string values, visible to users, not secure for sensitive data.
2. State (via NavigateOptions)#
State is hidden from the URL and stored in the browser’s history stack. It’s best for:
- Complex objects (e.g., user profiles, form data).
- Sensitive data (though not fully secure—avoid passwords!).
Pros: Supports any data type (objects, arrays), hidden from the URL.
Cons: Lost on page refresh (since history state is session-based), not shareable.
Fixing the NavigateOptions Type Error in TypeScript#
To resolve the "unknown state" error, we need to explicitly type the state property using TypeScript generics. Here’s how:
Step 1: Define a Type for Your State#
Create an interface or type alias to describe the structure of the state you want to pass. For example, if passing user data:
// Define the shape of the state
interface UserState {
user: {
id: number;
name: string;
email: string;
};
}Step 2: Type useNavigate with the State Type#
The useNavigate hook can be generic, allowing you to specify the type of state. Use useNavigate<UserState>() to tell TypeScript that the state will match the UserState interface:
import { useNavigate } from 'react-router-dom';
// Type useNavigate with the UserState interface
const navigate = useNavigate<UserState>();Step 3: Pass State Safely#
Now you can pass state without type errors. The navigate function will enforce that state matches UserState:
// Navigate to /profile and pass state
navigate('/profile', {
state: {
user: { id: 1, name: "John Doe", email: "[email protected]" }, // Matches UserState
},
replace: false, // Optional: push new history entry
});Step 4: Access State in the Target Route#
To access the state in the target route, use useLocation (from React Router) and explicitly type it with your UserState interface:
import { useLocation } from 'react-router-dom';
// Type useLocation with UserState
const location = useLocation<UserState>();
// Access the state (TypeScript now knows the structure!)
const { user } = location.state;
console.log(user.id); // 1 (TypeScript infers `user` as { id: number; name: string; email: string })Step-by-Step Implementation Example#
Let’s walk through a full example to solidify this. We’ll create a simple app with two routes:
Home: A button to navigate toProfilewith user data.Profile: Displays the user data passed viastate.
Step 1: Set Up Routes#
First, configure your router in App.tsx:
// App.tsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './Home';
import Profile from './Profile';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Router>
);
}
export default App;Step 2: Create the Home Component (with useNavigate)#
In Home.tsx, use useNavigate with a typed state to navigate to /profile:
// Home.tsx
import { useNavigate } from 'react-router-dom';
// Define the state type
interface UserState {
user: {
id: number;
name: string;
email: string;
};
}
const Home = () => {
// Type useNavigate with UserState
const navigate = useNavigate<UserState>();
const handleNavigate = () => {
// Pass state to /profile
navigate('/profile', {
state: {
user: { id: 1, name: "John Doe", email: "[email protected]" },
},
});
};
return (
<div>
<h1>Home</h1>
<button onClick={handleNavigate}>Go to Profile</button>
</div>
);
};
export default Home;Step 3: Create the Profile Component (Access State)#
In Profile.tsx, use useLocation to access the typed state:
// Profile.tsx
import { useLocation } from 'react-router-dom';
// Reuse the UserState interface (or import it from a shared types file)
interface UserState {
user: {
id: number;
name: string;
email: string;
};
}
const Profile = () => {
// Type useLocation with UserState
const location = useLocation<UserState>();
// Safely access state (TypeScript knows the structure)
const { user } = location.state || { user: { id: 0, name: "Guest", email: "[email protected]" } };
return (
<div>
<h1>Profile</h1>
<p>ID: {user.id}</p>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
</div>
);
};
export default Profile;Why This Works#
- By typing
useNavigate<UserState>, we tell TypeScript thestatepassed tonavigatemust matchUserState. - By typing
useLocation<UserState>, we ensure TypeScript recognizes the structure oflocation.state, eliminating "unknown" errors.
Common Pitfalls and Solutions#
1. "state is undefined" When Navigating Directly#
If a user navigates directly to /profile (e.g., via the address bar), location.state will be undefined. Always handle this case with fallback values:
// Safely access state with optional chaining or fallback
const user = location.state?.user || { id: 0, name: "Guest", email: "[email protected]" };2. State Lost on Page Refresh#
state is stored in the browser’s history stack, which is session-based. Refreshing the page or closing/reopening the browser will erase it. For persistent data, use query strings or a state management library (e.g., Redux, Zustand).
3. Query Strings: Parsing and Type Safety#
If using query strings (e.g., /profile?userId=1), use useSearchParams to parse them. Note that query parameters are always strings, so parse numbers/booleans explicitly:
// In Profile.tsx (for query strings)
import { useSearchParams } from 'react-router-dom';
const Profile = () => {
const [searchParams] = useSearchParams();
const userId = Number(searchParams.get('userId')); // Parse string to number
const isAdmin = searchParams.get('isAdmin') === 'true'; // Parse string to boolean
return <div>User ID: {userId}, Is Admin: {isAdmin}</div>;
};4. Overcomplicating State Types#
For simple cases (e.g., passing a single ID), you don’t need a complex interface. Use a primitive type directly:
// Navigate with a single ID
navigate<number>('/profile', { state: 123 });
// Access it
const location = useLocation<number>();
const userId = location.state; // Type: numberConclusion#
Passing parameters with useNavigate in React Router v6 (TypeScript) is straightforward once you understand how to type the state property. By explicitly defining the state type with TypeScript generics, you avoid "NavigateOptions" errors and ensure type safety.
Key takeaways:
- Use
useNavigate<StateType>()to type the navigation state. - Define interfaces for complex state objects to leverage TypeScript’s type inference.
- Prefer query strings for shareable/persistent data and
statefor hidden/complex data. - Always handle undefined state and parse query strings explicitly.