inertia-patterns
Enforce Inertia.js patterns in Vue components. Prevents axios usage, ensures proper form handling, and guides navigation patterns. Activates when working with data fetching, form submissions, or page navigation in Vue components.
What this skill does
# Inertia.js Patterns
Inertia is the ONLY acceptable way to handle data fetching and navigation in VILT stack.
## CRITICAL: No HTTP Clients
**NEVER import or use:**
- `axios`
- `fetch` (for API calls)
- Any HTTP client library
Inertia handles all server communication.
```typescript
// WRONG - Will be flagged as CRITICAL issue
import axios from 'axios'
const response = await axios.get('/api/users')
// WRONG
const response = await fetch('/api/users')
// CORRECT - Use Inertia
import { router } from '@inertiajs/vue3'
router.reload({ only: ['users'] })
```
## Data Fetching
### Props from Controller
Data comes from controller props, not API calls:
```typescript
// Props are passed by the Laravel controller
interface Props {
users: User[]
filters: Filters
}
const props = defineProps<Props>()
// Use props directly
const userCount = computed(() => props.users.length)
```
### Reloading Data
Use `router.reload()` to refresh specific props:
```typescript
import { router } from '@inertiajs/vue3'
// Reload only 'users' prop
router.reload({ only: ['users'] })
// Reload with new filters
router.reload({
only: ['users'],
data: {
search: searchQuery.value,
status: selectedStatus.value,
},
})
// Reload preserving scroll position
router.reload({
only: ['users'],
preserveScroll: true,
})
```
### Partial Reloads
Be specific about what data to reload:
```typescript
// CORRECT: Only reload what changed
router.reload({ only: ['bookings'] })
// AVOID: Full page reload
router.reload() // Reloads all props
```
## Form Handling
### useForm Hook
Always use `useForm()` for forms:
```typescript
import { useForm } from '@inertiajs/vue3'
const form = useForm({
name: '',
email: '',
role: 'user',
})
function submit() {
form.post(route('users.store'), {
preserveScroll: true,
onSuccess: () => {
form.reset()
},
onError: (errors) => {
// Handle validation errors
console.error(errors)
},
})
}
```
### Form Methods
Use appropriate HTTP methods:
```typescript
// Create
form.post(route('users.store'))
// Update
form.put(route('users.update', user.id))
form.patch(route('users.update', user.id))
// Delete
form.delete(route('users.destroy', user.id))
```
### Form State
Access form state properties:
```typescript
// Processing state (for loading indicators)
<Button :disabled="form.processing">
{{ form.processing ? 'Saving...' : 'Save' }}
</Button>
// Validation errors
<FloatingLabelInput
v-model="form.name"
:error="form.errors.name"
/>
// Check if form has been modified
const hasChanges = computed(() => form.isDirty)
// Reset form
form.reset()
form.reset('name', 'email') // Reset specific fields
// Clear errors
form.clearErrors()
form.clearErrors('name')
```
### Transform Data
Transform data before sending:
```typescript
const form = useForm({
startDate: null as Date | null,
amount: '',
})
function submit() {
form.transform((data) => ({
...data,
startDate: data.startDate?.toISOString(),
amount: parseFloat(data.amount),
})).post(route('bookings.store'))
}
```
## Navigation
### Programmatic Navigation
Use `router` for navigation:
```typescript
import { router } from '@inertiajs/vue3'
// Simple visit
router.visit('/users')
// Visit with method
router.visit('/users', { method: 'post', data: {} })
// Replace history (no back)
router.visit('/dashboard', { replace: true })
// Preserve state
router.visit('/users', {
preserveState: true,
preserveScroll: true,
})
```
### Link Component
Use Inertia's Link for navigation links:
```vue
<script setup lang="ts">
import { Link } from '@inertiajs/vue3'
</script>
<template>
<!-- Basic link -->
<Link :href="route('users.index')">Users</Link>
<!-- With method -->
<Link :href="route('logout')" method="post" as="button">
Logout
</Link>
<!-- Preserve scroll -->
<Link :href="route('users.show', user.id)" preserve-scroll>
{{ user.name }}
</Link>
</template>
```
## Shared Data
### usePage Hook
Access shared data from HandleInertiaRequests middleware:
```typescript
import { usePage } from '@inertiajs/vue3'
const page = usePage()
// Access auth user
const user = computed(() => page.props.auth.user)
// Access flash messages
const flash = computed(() => page.props.flash)
// Access any shared prop
const appName = computed(() => page.props.appName)
```
### Type Safety
Type the page props:
```typescript
interface PageProps {
auth: {
user: User | null
}
flash: {
success?: string
error?: string
}
}
const page = usePage<PageProps>()
```
## Events
### Before/After Events
Handle navigation events:
```typescript
import { router } from '@inertiajs/vue3'
// Before navigation starts
router.on('before', (event) => {
// Return false to cancel
if (!confirm('Leave page?')) {
return false
}
})
// Navigation started
router.on('start', (event) => {
showLoadingIndicator()
})
// Navigation finished
router.on('finish', (event) => {
hideLoadingIndicator()
})
// Successful response
router.on('success', (event) => {
console.log('Page loaded')
})
// Error response
router.on('error', (errors) => {
console.error(errors)
})
```
## Common Patterns
### Search/Filter
Implement search with debounce:
```typescript
import { watch, ref } from 'vue'
import { router } from '@inertiajs/vue3'
import { useDebounceFn } from '@vueuse/core'
const search = ref('')
const debouncedSearch = useDebounceFn(() => {
router.reload({
only: ['users'],
data: { search: search.value },
preserveState: true,
})
}, 300)
watch(search, debouncedSearch)
```
### Pagination
Handle pagination:
```typescript
function changePage(page: number) {
router.reload({
only: ['users'],
data: { page },
preserveState: true,
preserveScroll: true,
})
}
```
### Confirmation Dialogs
Handle destructive actions:
```typescript
function deleteUser(user: User) {
if (confirm(`Delete ${user.name}?`)) {
router.delete(route('users.destroy', user.id), {
preserveScroll: true,
})
}
}
```
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.