Skip to content

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 prop

Object Mode (Static Props) โ€‹

javascript
const routes = [
  {
    path: '/about',
    component: AboutPage,
    props: {
      title: 'About Us',
      version: '1.0.0',
      showContact: true
    }
  }
]

// AboutPage receives static props

Function 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
  • 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! ๐ŸŽ

๐Ÿš€ Vue Router - ่ฎฉๅ‰็ซฏ่ทฏ็”ฑๅ˜ๅพ—็ฎ€ๅ•่€Œๅผบๅคง | ๆž„ๅปบ็ŽฐไปฃๅŒ–ๅ•้กตๅบ”็”จ็š„ๆœ€ไฝณ้€‰ๆ‹ฉ