skip to content

In Vue Router 5, what do a route record's props: true, object and function modes pass to the component, and when does each fit?

level: middleimportance: should knowfreq 48%

answer

  1. decouple the component from useRoute
  2. true passes params only
  3. object passes static values
  4. function can cast and add query
  5. called inside RouterView's render

basics

~10 s

props: true passes route.params as props, an object passes fixed props as-is, and a function receives the route and returns props, which is how you cast ids to numbers or pass query values.

solid answer

~40 s

The record's `props` option lets `RouterView` hand route data to the component as ordinary props, so the component no longer calls `useRoute()` and becomes easier to reuse and test. `props: true` passes `route.params` as they are: strings, arrays for repeatable params, and nothing from the query. An **object** is passed as-is, which suits static flags like `{ compact: true }`. A **function** receives the route and returns the props, the place to cast `Number(route.params.id)` or pass `route.query.tab`. `RouterView` calls the function while it renders, so it runs on every route change; keep it a pure mapping from route to props, as the docs advise. With named views, `props` becomes a per-view map. Params you pass but do not declare fall through as attributes on the component's root element.

code

vue · 9 lines
vue
<script setup lang="ts">
// record: { path: '/products/:id', component: ProductPage,
//           props: route => ({ id: Number(route.params.id) }) }
const props = defineProps<{ id: number }>()
</script>

<template>
  <h1>Product #{{ props.id }}</h1>
</template>

go deeper

for a junior

Know that props: true passes route params as component props so the component can skip useRoute().

for a middle

Compare the boolean, object and function modes, including string values, missing query, and props updating on reuse.

for a senior

Use function mode to cast and default inputs, keep it pure, and catch undeclared params leaking as attributes.

for a principal

Set a convention that route components receive typed props, so views stay testable without a router.

## Why route props exist A component that calls `useRoute()` and reads `route.params.id` only works on URLs shaped like its route. The same product card cannot be dropped into a modal or a test without a router. The record's `props` option moves that coupling into the route table: `RouterView` computes props from the route and passes them to the component, which just declares `defineProps`. ## The three modes | Mode | Record | Component receives | |---|---|---| | Boolean | `props: true` | `route.params`, unchanged | | Object | `props: { compact: true }` | that object, as-is | | Function | `props: route => ({ ... })` | whatever the function returns | ### Boolean mode - Passes **every path param** under its own name: `/products/:id` gives the prop `id`. - Values are strings, or arrays for `+` and `*` params. The component should declare `id: string`, not `number`. - Query and hash are **not** included. ### Object mode - The object is passed as the props, with no link to the route. - It suits static configuration of a reused component: the same `ProductList` rendered at `/sale` with `{ onSaleOnly: true }` and at `/products` without it. ### Function mode - The function receives the resolved route and returns an object. - Use it to **cast** (`Number(route.params.id)`), to **rename**, to **combine** params with query values or constants, and to apply defaults. - The router docs ask you to keep it **stateless**. `RouterView` calls it inside its own render function, so it runs on every route change and on any other re-render of that `RouterView`. Reactive state read inside it silently becomes a render dependency of `RouterView`, plain variables are not tracked at all, and side effects would repeat unpredictably. If props depend on app state, read that state in the component, or wrap the view in a small component that reads it. ```ts { path: '/products/:id', component: ProductPage, props: (route) => ({ id: Number(route.params.id), tab: typeof route.query.tab === 'string' ? route.query.tab : 'overview', }), } ``` ## Behaviour worth knowing 1. **Props update on reuse.** Going from `/products/1` to `/products/2` reuses the component and updates its props; watch `() => props.id` to reload data. 2. **Undeclared props fall through.** `RouterView` passes everything the mode produces. A param the component does not declare becomes a fallthrough attribute: with `props: true`, an undeclared `id` renders as an `id="42"` HTML attribute on a single-root component's root element. 3. **Named views need a map.** A record with `components: { default, sidebar }` takes `props: { default: true, sidebar: false }`; a bare `true` there applies to every view. 4. **Each record owns its own props.** In nested routes, a parent's `props` option does not flow into the child; each record declares its own. 5. **Redirect records have none.** A record that only redirects renders nothing, so `props` there does nothing. ## RouterView attributes are not a substitute Attributes on `<RouterView>`, or on the `<component :is>` inside its slot, go to **every** view that renders there. The router docs discourage that for data: every view would have to declare the prop. Route-level `props` keeps each view's inputs next to its own record. ## The payoff in tests The reason to bother shows up in unit tests and component showcases: - A props-driven `ProductPage` mounts with `mount(ProductPage, { props: { id: 42 } })`, with no router, no memory history and no navigation to await. - The props function itself is a plain function of a route object, so it can be tested with `router.resolve('/products/42?tab=specs')` as input and a simple equality check on the output. - A component that calls `useRoute()` needs a real router installed and a navigation completed before every assertion. ## Choosing - `props: true` when the params already have the right names and types for the component. - Function mode when anything must be converted, renamed, defaulted or taken from the query, which in practice is most detail pages with numeric ids. - Object mode for static flags that make one component serve several routes. - Keep `useRoute()` for components that genuinely care about the whole route, such as breadcrumbs.

  • The router docs say a props function should stay stateless. Why, given how RouterView uses it?
    `RouterView` calls the function inside its render, so it runs on every navigation and on any other re-render of the view. Reactive state read there becomes a hidden render dependency of `RouterView`, plain variables are not tracked, and side effects repeat. Keep it a pure mapping from route to props and read app state in the component or a small wrapper.
  • With props: true on /products/:id, the rendered root div suddenly has id="42". What happened?
    The component did not declare an `id` prop, so the value passed by `RouterView` became a fallthrough attribute and landed on the root element. Declare the prop, or use a props function that passes only what the component declares.

saying these in an interview costs you the question

  • props: true also passes query parameters as props.
  • props: true converts numeric params to numbers.
  • A props function runs once when the route table is created and is cached.
  • Object mode maps route params onto the object's keys.
  • Passing a prop on RouterView is the recommended way to feed one view.