# Navigation and Routing
## Contents
- [Pre-Implementation Requirements for pnpm](#pre-implementation-requirements-for-pnpm)
- [Basic Navigation with Link Component](#basic-navigation-with-link-component)
- [Programmatic Navigation](#programmatic-navigation)
- [Accessing Route Parameters](#accessing-route-parameters)
- [Linking to Built-in Admin Pages](#linking-to-built-in-admin-pages)
- [Navigation from Widgets](#navigation-from-widgets)
- [Common Navigation Patterns](#common-navigation-patterns)
## Pre-Implementation Requirements for pnpm
**⚠️ pnpm Users**: Navigation requires `react-router-dom`. Install BEFORE implementing:
```bash
pnpm list react-router-dom --depth=10 | grep @medusajs/dashboard
pnpm add react-router-dom@[exact-version]
```
**npm/yarn Users**: DO NOT install - already available through dashboard dependencies.
## Basic Navigation with Link Component
Use the `Link` component for internal navigation in widgets and custom pages:
```tsx
import { Link } from "react-router-dom"
import { Text } from "@medusajs/ui"
import { TriangleRightMini } from "@medusajs/icons"
// Link to a custom page
Go to Custom Page
```
### Link with Dynamic ID
```tsx
// Link to product details
{product.title}
```
### Button-styled Link
```tsx
import { Button } from "@medusajs/ui"
import { Link } from "react-router-dom"
```
## Programmatic Navigation
Use `useNavigate` for navigation after actions (e.g., after creating an entity):
```tsx
import { useNavigate } from "react-router-dom"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { toast, Button } from "@medusajs/ui"
import { sdk } from "../lib/client"
const CreateProductWidget = () => {
const navigate = useNavigate()
const queryClient = useQueryClient()
const createProduct = useMutation({
mutationFn: (data) => sdk.admin.product.create(data),
onSuccess: (result) => {
queryClient.invalidateQueries({ queryKey: ["products"] })
toast.success("Product created successfully")
// Navigate to the new product's page
navigate(`/products/${result.product.id}`)
},
onError: (error) => {
toast.error(error.message || "Failed to create product")
},
})
const handleCreate = () => {
createProduct.mutate({
title: "New Product",
// ... other fields
})
}
return (
)
}
```
### Navigate with State
Pass data to the destination page:
```tsx
navigate("/custom/review", {
state: { productId: product.id, productTitle: product.title }
})
// Access in destination page
import { useLocation } from "react-router-dom"
const ReviewPage = () => {
const location = useLocation()
const { productId, productTitle } = location.state || {}
return
Reviewing: {productTitle}
}
```
### Navigate Back
```tsx
const navigate = useNavigate()
```
## Accessing Route Parameters
In custom pages, access URL parameters with `useParams`:
```tsx
// Custom page at: /custom/products/:id
import { useParams } from "react-router-dom"
import { useQuery } from "@tanstack/react-query"
import { sdk } from "../lib/client"
import { Container, Heading } from "@medusajs/ui"
const ProductDetailsPage = () => {
const { id } = useParams() // Get :id from URL
const { data: product, isLoading } = useQuery({
queryFn: () => sdk.admin.product.retrieve(id, {
fields: "+metadata,+variants.*",
}),
queryKey: ["product", id],
enabled: !!id, // Only fetch if ID exists
})
if (isLoading) return
)
}
```
## Linking to Built-in Admin Pages
Link to standard Medusa admin pages:
```tsx
import { Link } from "react-router-dom"
// Product details
View Product
// Order details
View Order
// Customer details
View Customer
// Product categories
View Categories
// Settings
Settings
// Custom field in settings
Custom Settings
```
### Common Built-in Routes
```tsx
const ADMIN_ROUTES = {
products: "/products",
productDetails: (id: string) => `/products/${id}`,
orders: "/orders",
orderDetails: (id: string) => `/orders/${id}`,
customers: "/customers",
customerDetails: (id: string) => `/customers/${id}`,
categories: "/categories",
inventory: "/inventory",
pricing: "/pricing",
settings: "/settings",
}
// Usage
View Product
```
## Navigation from Widgets
### Pattern: View All Link
Add a "View All" link from a widget to a custom page:
```tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { Container, Heading, Button, Text } from "@medusajs/ui"
import { Link } from "react-router-dom"
import { DetailWidgetProps } from "@medusajs/framework/types"
const RelatedProductsWidget = ({ data: product }) => {
// ... widget logic
return (
Related Products
{/* Widget content */}
)
}
export const config = defineWidgetConfig({
zone: "product.details.after",
})
export default RelatedProductsWidget
```
### Pattern: List Item Navigation
Make list items clickable to navigate:
```tsx
import { Thumbnail, Text } from "@medusajs/ui"
import { TriangleRightMini } from "@medusajs/icons"
import { Link } from "react-router-dom"
const ProductListItem = ({ product }) => {
return (
{product.title}
{product.status}
)
}
```
## Common Navigation Patterns
### Pattern: Back to List
Navigate back to list after viewing details:
```tsx
import { useNavigate } from "react-router-dom"
import { ArrowLeft } from "@medusajs/icons"
import { IconButton } from "@medusajs/ui"
const DetailsPage = () => {
const navigate = useNavigate()
return (
navigate("/custom/products")}>
Product Details
{/* Details content */}
)
}
```
### Pattern: Breadcrumb Navigation
```tsx
import { Link } from "react-router-dom"
import { Text } from "@medusajs/ui"
const Breadcrumbs = ({ product }) => {
return (
Products
/
{product.title}
/Details
)
}
```
### Pattern: Tab Navigation
Navigate between different views using tabs:
```tsx
import { useSearchParams, Link } from "react-router-dom"
import { Tabs } from "@medusajs/ui"
const ProductTabs = () => {
const [searchParams] = useSearchParams()
const activeTab = searchParams.get("tab") || "details"
return (
Details
Variants
Media
{/* Details content */}
{/* Variants content */}
{/* Media content */}
)
}
```
### Pattern: Action with Navigation
Perform an action then navigate:
```tsx
const deleteProduct = useMutation({
mutationFn: (id) => sdk.admin.product.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["products"] })
toast.success("Product deleted")
navigate("/products") // Navigate to list after deletion
},
})
```
### Pattern: Conditional Navigation
Navigate based on state or data:
```tsx
const handleComplete = () => {
if (hasErrors) {
toast.error("Please fix errors first")
return
}
if (isDraft) {
navigate(`/custom/products/${id}/publish`)
} else {
navigate("/products")
}
}
```
## Important Notes
1. **pnpm users**: Must install `react-router-dom` with exact version from dashboard
2. **npm/yarn users**: Do NOT install `react-router-dom` - already available
3. **Always use relative paths** starting with `/` for internal navigation
4. **Use Link for navigation links** - better for SEO and accessibility
5. **Use navigate for programmatic navigation** - after actions or based on logic
6. **Always handle loading states** when fetching route parameter-based data
7. **Clean up on unmount** when using listeners or subscriptions in routes
8. **Maintain focus management** for accessibility when navigating