Using Inertia.js Partial Reloads to Keep UI State While Updating Data
Learn how Inertia.js partial reloads let you fetch only the props you need while preserving client‑side state, with a concrete Laravel/Vue example and notes on shallow merge limits.
20 Aug 2025, 03:51 UTC

Problem: Full page reloads lose client‑side state
When you navigate with Inertia.js using router.visit() or router.reload() without options, the server returns a complete page payload. Even if you only need to refresh a table after creating a record, the entire layout, navigation menus, and any shared data are sent again. This extra bandwidth is usually harmless, but it also causes the browser to remount every component, which clears scroll positions, form inputs, and any local UI state (e.g., an open dropdown or a temporary filter).
Thesis: Partial reloads let you fetch only the props you need while preserving the rest of the page
Inertia.js provides the only and except options on the router methods. When you pass an array of prop names, the server returns a JSON subset containing just those keys (plus the required version and component fields). Components whose props are not included are not re‑rendered, so their internal state stays intact. This pattern reduces payload size and avoids the visual flicker of a full remount.
How partial reloads work
Client side
You call the router with the only (or except) option:
import { usePage, router } from '@inertiajs/vue3'
// Example: refresh the users table after a create action
const createUser = async (form) => {
await post('/users', form)
// Ask the server for only the 'users' prop
router.reload({ only: ['users'] })
}
The request includes two signals that the server can inspect:
X-Inertia-Partial-Dataheader set totrueonly(orexcept) query parameter containing the array you passed
Server side (Laravel example)
The controller can detect a partial request and shape the response accordingly:
use Illuminate\Http\Request;
use Inertia\Inertia;
public function index(Request $request)
{
$users = User::orderBy('created_at', 'desc')->paginate(15);
$stats = [
'total' => User::count(),
'active' => User::where('active', true)->count(),
];
if ($request->header('X-Inertia-Partial-Data')) {
$only = $request->input('only', []);
$except = $request->input('except', []);
$data = [];
if (!empty($only)) {
foreach ($only as $key) {
if ($key === 'users') $data['users'] = $users;
if ($key === 'stats') $data['stats'] = $stats;
}
} elseif (!empty($except)) {
// start with all props then remove the excepted ones
$data = ['users' => $users, 'stats' => $stats];
foreach ($except as $key) {
unset($data[$key]);
}
} else {
// fallback to full shape
$data = ['users' => $users, 'stats' => $stats];
}
return Inertia::render('Users/Index', $data);
}
// full page visit
return Inertia::render('Users/Index', [
'users' => $users,
'stats' => $stats,
]);
}
If you prefer not to write conditionals, Inertia’s built‑in helper can do the same:
return Inertia::render('Users/Index', [
'users' => $users,
'stats' => $stats,
])->only($request->input('only'));
Worked example: preserving a filter input
Imagine a page with a shared layout that contains a global search bar. Below the layout you have a UsersTable component that receives the users prop. You want to clear the table and fetch fresh data when the user clicks “Refresh”, but you do not want to lose the text they typed in the search bar.
<template>
<div>
<input
v-model="search"
placeholder="Search…"
@input="debouncedSearch"
/>
<UsersTable :users="users" />
<button @click="refreshTable">Refresh</button>
</div>
</template>
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.