Passing Props | Vue Router - Component Props from Routes ๐ โ
Clean component separation - Pass route parameters as props to keep components decoupled from routing logic!
๐ฏ Overview โ
Vue Router allows you to pass route parameters as props to your components, creating a clean separation between routing logic and component functionality. This approach makes components more reusable, testable, and easier to reason about.
๐ Key Benefits โ
- Decoupled Components: Components don't need to know about routing
- Better Reusability: Use components outside of routing contexts
- Easier Testing: Test components with direct prop injection
- Cleaner Code: Separate routing concerns from component logic
๐ก When to Use Props Passing โ
๐ Reusable Components โ
Data-Driven Components
- Components that display data from URLs
- List and detail views
- Search and filter interfaces
Independent Testing
- Components that need unit testing
- Storybook documentation
- Component playgrounds
๐ง Basic Implementation โ
vue
<template>
<!-- User component receives userId as prop -->
<div class="user-profile">
<h2>User Profile: {{ userId }}</h2>
<!-- Component logic here -->
</div>
</template>
<script setup>
// Component receives props directly, no routing dependency
defineProps({
userId: {
type: String,
required: true
}
})
</script>๐ Basic Configuration โ
Boolean Mode (Simple Props) โ
javascript
// router/index.js
const routes = [
{
path: '/user/:userId',
component: UserProfile,
props: true // Pass route.params as props
}
]
// UserProfile.vue receives { userId } as propObject Mode (Static Props) โ
javascript
const routes = [
{
path: '/about',
component: AboutPage,
props: {
title: 'About Us',
version: '1.0.0',
showContact: true
}
}
]
// AboutPage receives static propsFunction Mode (Dynamic Props) โ
javascript
const routes = [
{
path: '/search/:query',
component: SearchResults,
props: (route) => ({
query: route.params.query,
page: parseInt(route.query.page) || 1,
sort: route.query.sort || 'relevance'
})
}
]
// SearchResults receives computed props๐ง Advanced Techniques โ
Example 1: E-commerce Product Page with Multiple Props โ
javascript
// router/products.js
const productRoutes = [
{
path: '/products/:category/:productId',
component: () => import('./views/ProductDetail.vue'),
props: (route) => {
// Transform and validate route parameters
const category = route.params.category.toLowerCase()
const productId = parseInt(route.params.productId)
const variant = route.query.variant || 'default'
const showReviews = route.query.reviews !== 'false'
// Return structured props
return {
category,
productId,
variant,
showReviews,
// Additional computed values
isOnSale: route.query.sale === 'true',
currency: route.query.currency || 'USD'
}
}
}
]ProductDetail Component โ
vue
<template>
<div class="product-detail">
<ProductHeader
:category="category"
:product-id="productId"
:variant="variant"
/>
<ProductGallery :product-id="productId" />
<ProductInfo
:product-id="productId"
:variant="variant"
:is-on-sale="isOnSale"
:currency="currency"
/>
<ProductReviews
v-if="showReviews"
:product-id="productId"
/>
<RelatedProducts :category="category" />
</div>
</template>
<script setup>
defineProps({
category: {
type: String,
required: true,
validator: (value) => ['electronics', 'clothing', 'books'].includes(value)
},
productId: {
type: Number,
required: true,
validator: (value) => value > 0
},
variant: {
type: String,
default: 'default'
},
showReviews: {
type: Boolean,
default: true
},
isOnSale: Boolean,
currency: {
type: String,
default: 'USD'
}
})
</script>Example 2: Advanced Search with Filter Props โ
javascript
// router/search.js
const searchRoutes = [
{
path: '/search',
component: () => import('./views/AdvancedSearch.vue'),
props: (route) => {
// Parse complex search parameters
const query = route.query.q || ''
const filters = parseFilters(route.query.filters)
const sortBy = route.query.sort || 'relevance'
const viewMode = route.query.view || 'grid'
const resultsPerPage = parseInt(route.query.limit) || 20
const currentPage = parseInt(route.query.page) || 1
return {
query,
filters,
sortBy,
viewMode,
resultsPerPage,
currentPage,
// Computed values
hasFilters: Object.keys(filters).length > 0,
isAdvancedSearch: query.includes(':') || Object.keys(filters).length > 0
}
}
}
]
function parseFilters(filterString) {
if (!filterString) return {}
try {
return JSON.parse(decodeURIComponent(filterString))
} catch {
return {}
}
}AdvancedSearch Component โ
vue
<template>
<div class="advanced-search">
<SearchHeader
:query="query"
:has-filters="hasFilters"
:is-advanced-search="isAdvancedSearch"
@search="handleSearch"
/>
<SearchFilters
:filters="filters"
:query="query"
@update:filters="updateFilters"
/>
<SearchResults
:query="query"
:filters="filters"
:sort-by="sortBy"
:view-mode="viewMode"
:results-per-page="resultsPerPage"
:current-page="currentPage"
/>
<SearchPagination
:current-page="currentPage"
:results-per-page="resultsPerPage"
:total-results="totalResults"
@page-change="handlePageChange"
/>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
query: String,
filters: Object,
sortBy: String,
viewMode: String,
resultsPerPage: Number,
currentPage: Number,
hasFilters: Boolean,
isAdvancedSearch: Boolean
})
const router = useRouter()
const totalResults = ref(0)
// Watch for prop changes and update URL
watch(() => props.currentPage, (newPage) => {
updateURL({ page: newPage })
})
watch(() => props.sortBy, (newSort) => {
updateURL({ sort: newSort })
})
function updateURL(updates) {
const currentQuery = { ...router.currentRoute.value.query, ...updates }
router.push({ query: currentQuery })
}
function handleSearch(newQuery) {
updateURL({ q: newQuery, page: 1 })
}
function updateFilters(newFilters) {
const filterString = encodeURIComponent(JSON.stringify(newFilters))
updateURL({ filters: filterString, page: 1 })
}
function handlePageChange(newPage) {
updateURL({ page: newPage })
}
</script>๐ ๏ธ Practical Examples โ
Example 3: User Dashboard with Context-Aware Props โ
javascript
// router/dashboard.js
const dashboardRoutes = [
{
path: '/dashboard/:tab',
component: () => import('./views/UserDashboard.vue'),
props: (route) => {
const tab = route.params.tab || 'overview'
const userId = route.query.userId || getCurrentUserId()
const timeRange = route.query.range || '7d'
const viewType = route.query.view || 'summary'
// Validate and transform parameters
const validTabs = ['overview', 'analytics', 'settings', 'billing']
const validatedTab = validTabs.includes(tab) ? tab : 'overview'
return {
activeTab: validatedTab,
userId: parseInt(userId),
timeRange,
viewType,
// Context information
isAdmin: checkAdminPermissions(userId),
canEdit: checkEditPermissions(userId),
// Feature flags
showBetaFeatures: route.query.beta === 'true',
enableExport: route.query.export !== 'false'
}
}
}
]UserDashboard Component โ
vue
<template>
<div class="user-dashboard">
<DashboardHeader
:active-tab="activeTab"
:user-id="userId"
:is-admin="isAdmin"
@tab-change="changeTab"
/>
<DashboardSidebar
:active-tab="activeTab"
:user-id="userId"
:can-edit="canEdit"
/>
<main class="dashboard-content">
<component
:is="currentTabComponent"
:user-id="userId"
:time-range="timeRange"
:view-type="viewType"
:show-beta-features="showBetaFeatures"
:enable-export="enableExport"
/>
</main>
<DashboardFooter
:active-tab="activeTab"
:user-id="userId"
/>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
activeTab: String,
userId: Number,
timeRange: String,
viewType: String,
isAdmin: Boolean,
canEdit: Boolean,
showBetaFeatures: Boolean,
enableExport: Boolean
})
const router = useRouter()
const currentTabComponent = computed(() => {
const components = {
overview: () => import('./components/OverviewTab.vue'),
analytics: () => import('./components/AnalyticsTab.vue'),
settings: () => import('./components/SettingsTab.vue'),
billing: () => import('./components/BillingTab.vue')
}
return components[props.activeTab] || components.overview
})
function changeTab(newTab) {
router.push(`/dashboard/${newTab}?userId=${props.userId}`)
}
</script>๐ Advanced Patterns โ
Props with Route Meta Combination โ
javascript
const routes = [
{
path: '/admin/:section',
component: AdminSection,
props: (route) => ({
section: route.params.section,
permissions: route.meta.requiredPermissions,
layout: route.meta.layout
}),
meta: {
requiredPermissions: ['admin', 'super_admin'],
layout: 'admin'
}
}
]Conditional Props Based on Route State โ
javascript
const routes = [
{
path: '/editor/:documentId',
component: DocumentEditor,
props: (route) => {
const isNew = route.query.new === 'true'
const template = route.query.template
return {
documentId: isNew ? null : route.params.documentId,
template: template || 'default',
isNew,
// Conditional props
autoSave: route.query.autosave !== 'false',
collaboration: route.query.collab === 'true'
}
}
}
]๐ Best Practices โ
โ Do โ
- Use Prop Validation: Define prop types and validators
- Keep Props Simple: Avoid complex object structures
- Document Prop Usage: Comment on prop purposes and expected values
- Test Prop Scenarios: Test components with various prop combinations
- Use Default Values: Provide sensible defaults for optional props
โ Don't โ
- Overuse Function Mode: Keep prop functions simple and predictable
- Ignore Performance: Be mindful of expensive computations in prop functions
- Forget Validation: Always validate incoming route parameters
- Mix Concerns: Keep routing logic separate from business logic
๐ Related Features โ
- Route Parameters: The source of prop data
- Route Query: Additional data for props
- Component Props: Vue's prop system
- Route Meta: Additional route information for props
๐ก Pro Tip
Combine props passing with Vue's Composition API to create highly reusable, testable components that can work both within your router and as standalone components with direct prop injection!
Ready for clean component architecture? Start using props passing to create decoupled, reusable components that are easy to test and maintain! ๐