Skip to content

useRouteRecord ​

Loads the record a detail page's :id names, loads again when only the id changes, and tells you when there is nothing to show.

Open in demo

Import ​

ts
import { useRouteRecord } from '@shared/composables/useRouteRecord'

Signature ​

ts
function useRouteRecord<Id>(
  item: Ref<unknown>,
  fetchOne: (id: Id) => Promise<void>,
): {
  id: ComputedRef<Id>
  notFound: ComputedRef<boolean>
}

item and fetchOne are the pair useCrud returns, or any ref and loader of your own that fills it.

Returns ​

MemberTypeMeaning
idComputedRef<Id>The current :id param.
notFoundComputedRef<boolean>true once a load has finished and item is still empty.

Example ​

vue
<script setup lang="ts">
import type { FieldDefinition } from '@core/schema/types'
import { useCrud } from '@core/crud/useCrud'
import DetailPageLayout from '@shared/components/common/pages/DetailPageLayout.vue'
import EmptyState from '@shared/components/feedback/EmptyState.vue'
import { useRouteRecord } from '@shared/composables/useRouteRecord'
import { computed } from 'vue'

interface Warehouse extends Record<string, unknown> {
  id: string
  name: string
  city: string
}

const schema: FieldDefinition[] = [
  { key: 'name', type: 'text', label: 'Name', required: true },
  { key: 'city', type: 'text', label: 'City', required: true },
]

const { item, fetchOne } = useCrud<Warehouse>({
  entity: 'warehouse',
  baseUrl: '/warehouses',
  schema,
})

const { notFound } = useRouteRecord<string>(item, fetchOne)

const title = computed(() => item.value?.name ?? '')
</script>

<template>
  <DetailPageLayout :title="title">
    <template #main>
      <EmptyState
        v-if="notFound"
        :title="$t('common.recordNotFound')"
        :description="$t('common.recordNotFoundHint')"
      />
      <p v-else-if="item">
        {{ item.city }}
      </p>
    </template>
  </DetailPageLayout>
</template>

Notes ​

  • It loads again when only the id changes. vue-router reuses the page component when you go from /projects/prj_1 to /projects/prj_2, so a load in onMounted would keep the first record on screen under the second address. This watches the param instead.
  • The old record is cleared first. item is set to null before each load, so the page never shows one record under another record's address, even for a moment.
  • A 404 means not found. A 404 from the API, or an answer with an empty body, ends in notFound. Any other error is thrown again, so your error handling still sees it.
  • Only its own route counts. When the reader leaves for another page that also has an :id, the param changes before this page unmounts. The watcher ignores any route but the one the page was created on, so it does not fetch on the way out.
  • The last request wins. When the id changes twice quickly, only the newest load settles notFound.
  • Forty-one detail pages in the demo modules use it. An unknown id shows "Record not found", with the edit and delete actions hidden.