Inertia React Development
When to Apply
Activate this skill when:
- Creating or modifying React page components for Inertia
- Working with forms in React (using
<Form>, useForm, or useHttp)
- Implementing client-side navigation with
<Link> or router
- Using v3 features: deferred props, prefetching, optimistic updates, instant visits, layout props, HTTP requests, WhenVisible, InfiniteScroll, once props, flash data, or polling
- Building React-specific features with the Inertia protocol
Documentation
Use search-docs for detailed Inertia v3 React patterns and documentation.
Basic Usage
Page Components Location
React page components should be placed in the resources/js/pages directory.
Page Component Structure
<!-- Basic React Page Component -->
export default function UsersIndex({ users }) {
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
</div>
)
}
Client-Side Navigation
Basic Link Component
Use <Link> for client-side navigation instead of traditional <a> tags:
<!-- Inertia React Navigation -->
import { Link, router } from '@inertiajs/react'
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
<Link href={`/users/${user.id}`}>View User</Link>
Link with Method
<!-- Link with POST Method -->
import { Link } from '@inertiajs/react'
<Link href="/logout" method="post" as="button">
Logout
</Link>
Prefetching
Prefetch pages to improve perceived performance:
<!-- Prefetch on Hover -->
import { Link } from '@inertiajs/react'
<Link href="/users" prefetch>
Users
</Link>
Programmatic Navigation
<!-- Router Visit -->
import { router } from '@inertiajs/react'
function handleClick() {
router.visit('/users')
}
// Or with options
router.visit('/users', {
method: 'post',
data: { name: 'John' },
onSuccess: () => console.log('Success!'),
})
Form Handling
Form Component (Recommended)
The recommended way to build forms is with the <Form> component:
<!-- Form Component Example -->
import { Form } from '@inertiajs/react'
export default function CreateUser() {
return (
<Form action="/users" method="post">
{({ errors, processing, wasSuccessful }) => (
<>
<input type="text" name="name" />
{errors.name && <div>{errors.name}</div>}
<input type="email" name="email" />
{errors.email && <div>{errors.email}</div>}
<button type="submit" disabled={processing}>
{processing ? 'Creating...' : 'Create User'}
</button>
{wasSuccessful && <div>User created!</div>}
</>
)}
</Form>
)
}
Form Component With All Props
<!-- Form Component Full Example -->
import { Form } from '@inertiajs/react'
<Form action="/users" method="post">
{({
errors,
hasErrors,
processing,
progress,
wasSuccessful,
recentlySuccessful,
clearErrors,
resetAndClearErrors,
defaults,
isDirty,
reset,
submit
}) => (
<>
<input type="text" name="name" defaultValue={defaults.name} />
{errors.name && <div>{errors.name}</div>}
<button type="submit" disabled={processing}>
{processing ? 'Saving...' : 'Save'}
</button>
{progress && (
<progress value={progress.percentage} max="100">
{progress.percentage}%
</progress>
)}
{wasSuccessful && <div>Saved!</div>}
</>
)}
</Form>
Form Component Reset Props
The <Form> component supports automatic resetting:
resetOnError - Reset form data when the request fails
resetOnSuccess - Reset form data when the request succeeds
setDefaultsOnSuccess - Update default values on success
Use the search-docs tool with a query of form component resetting for detailed guidance.
<!-- Form with Reset Props -->
import { Form } from '@inertiajs/react'
<Form
action="/users"
method="post"
resetOnSuccess
setDefaultsOnSuccess
>
{({ errors, processing, wasSuccessful }) => (
<>
<input type="text" name="name" />
{errors.name && <div>{errors.name}</div>}
<button type="submit" disabled={processing}>
Submit
</button>
</>
)}
</Form>
Forms can also be built using the useForm helper for more programmatic control. Use the search-docs tool with a query of useForm helper for guidance.
useForm Hook
For more programmatic control or to follow existing conventions, use the useForm hook:
<!-- useForm Hook Example -->
import { useForm } from '@inertiajs/react'
export default function CreateUser() {
const { data, setData, post, processing, errors, reset } = useForm({
name: '',
email: '',
password: '',
})
function submit(e) {
e.preventDefault()
post('/users', {
onSuccess: () => reset('password'),
})
}
return (
<form onSubmit={submit}>
<input
type="text"
value={data.name}
onChange={e => setData('name', e.target.value)}
/>
{errors.name && <div>{errors.name}</div>}
<input
type="email"
value={data.email}
onChange={e => setData('email', e.target.value)}
/>
{errors.email && <div>{errors.email}</div>}
<input
type="password"
value={data.password}
onChange={e => setData('password', e.target.value)}
/>
{errors.password && <div>{errors.password}</div>}
<button type="submit" disabled={processing}>
Create User
</button>
</form>
)
}
Inertia v3 Features
HTTP Requests
Use the useHttp hook for standalone HTTP requests that do not trigger Inertia page visits. It provides the same developer experience as useForm, but for plain JSON endpoints.
<!-- useHttp Example -->
import { useHttp } from '@inertiajs/react'
export default function Search() {
const { data, setData, get, processing } = useHttp({
query: '',
})
function search(e) {
setData('query', e.target.value)
get('/api/search', {
onSuccess: (response) => {
console.log(response)
},
})
}
return (
<>
<input value={data.query} onChange={search} />
{processing && <div>Searching...</div>}
</>
)
}
Optimistic Updates
Apply data changes instantly before the server responds, with automatic rollback on failure:
<!-- Optimistic Update with Router -->
import { router } from '@inertiajs/react'
function like(post) {
router.optimistic((props) => ({
post: {
...props.post,
likes: props.post.likes + 1,
},
})).post(`/posts/${post.id}/like`)
}
Optimistic updates also work with useForm and the <Form> component:
<!-- Optimistic Update with Form Component -->
import { Form } from '@inertiajs/react'
<Form
action="/todos"
method="post"
optimistic={(props, data) => ({
todos: [...props.todos, { id: Date.now(), name: data.name, done: false }],
})}
>
<input type="text" name="name" />
<button type="submit">Add Todo</button>
</Form>
Instant Visits
Navigate to a new page immediately without waiting for the server response. The target component renders right away with shared props, while page-specific props load in the background.
<!-- Instant Visit with Link -->
import { Link } from '@inertiajs/react'
<Link href="/dashboard" component="Dashboard">Dashboard</Link>
<Link
href="/posts/1"
component="Posts/Show"
pageProps={{ post: { id: 1, title: 'My Post' } }}
>
View Post
</Link>
Layout Props
Share dynamic data between pages and persistent layouts:
<!-- Layout Props in Layout -->
export default function Layout({ title = 'My App', showSidebar = true, children }) {
return (
<>
<header>{title}</header>
{showSidebar && <aside>Sidebar</aside>}
<main>{children}</main>
</>
)
}
<!-- Setting Layout Props from Page -->
import { setLayoutProps } from '@inertiajs/react'
export default function Dashboard() {
setLayoutProps({
title: 'Dashboard',
showSidebar: false,
})
return <h1>Dashboard</h1>
}
Deferred Props
Use deferred props to load data after initial page render:
<!-- Deferred Props with Empty State -->
export default function UsersIndex({ users }) {
return (
<div>
<h1>Users</h1>
{!users ? (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
) : (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
Polling
Use the usePoll hook to automatically refresh data at intervals. It handles cleanup on unmount and throttles polling when the tab is inactive.
<!-- Basic Polling -->
import { usePoll } from '@inertiajs/react'
export default function Dashboard({ stats }) {
usePoll(5000)
return (
<div>
<h1>Dashboard</h1>
<div>Active Users: {stats.activeUsers}</div>
</div>
)
}
<!-- Polling With Request Options and Manual Control -->
import { usePoll } from '@inertiajs/react'
export default function Dashboard({ stats }) {
const { start, stop } = usePoll(5000, {
only: ['stats'],
onStart() {
console.log('Polling request started')
},
onFinish() {
console.log('Polling request finished')
},
}, {
autoStart: false,
keepAlive: true,
})
return (
<div>
<h1>Dashboard</h1>
<div>Active Users: {stats.activeUsers}</div>
<button onClick={start}>Start Polling</button>
<button onClick={stop}>Stop Polling</button>
</div>
)
}
autoStart (default true) - set to false to start polling manually via the returned start() function
keepAlive (default false) - set to true to prevent throttling when the browser tab is inactive
WhenVisible
Lazy-load a prop when an element scrolls into view. Useful for deferring expensive data that sits below the fold:
<!-- WhenVisible Example -->
import { WhenVisible } from '@inertiajs/react'
export default function Dashboard({ stats }) {
return (
<div>
<h1>Dashboard</h1>
<WhenVisible data="stats" buffer={200} fallback={<div className="animate-pulse">Loading stats...</div>}>
{({ fetching }) => (
<div>
<p>Total Users: {stats.total_users}</p>
<p>Revenue: {stats.revenue}</p>
{fetching && <span>Refreshing...</span>}
</div>
)}
</WhenVisible>
</div>
)
}
InfiniteScroll
Automatically load additional pages of paginated data as users scroll:
<!-- InfiniteScroll Example -->
import { InfiniteScroll } from '@inertiajs/react'
export default function Users({ users }) {
return (
<InfiniteScroll data="users">
{users.data.map(user => (
<div key={user.id}>{user.name}</div>
))}
</InfiniteScroll>
)
}
The server must use Inertia::scroll() to configure the paginated data. Use the search-docs tool with a query of infinite scroll for detailed guidance on buffers, manual loading, reverse mode, and custom trigger elements.
Server-Side Patterns
Server-side patterns (Inertia::render, props, middleware) are covered in inertia-laravel guidelines.
Common Pitfalls
- Using traditional
<a> links instead of Inertia's <Link> component (breaks SPA behavior)
- Forgetting to add loading states (skeleton screens) when using deferred props
- Not handling the
undefined state of deferred props before data loads
- Using
<form> without preventing default submission (use <Form> component or e.preventDefault())
- Forgetting to check if
<Form> component is available in your Inertia version
- Using
router.cancel() instead of router.cancelAll() (v3 breaking change)
- Using
router.on('invalid', ...) or router.on('exception', ...) instead of the renamed httpException and networkError events