inertia
inertia copied to clipboard
Data hydratation issue with partial reload on initial page visit
Version:
-
@inertiajs/react
version: 1.1.0
Describe the problem:
Implementing lazy loading with Inertia::lazy in Laravel works well for asynchronous data fetching and improves Time To First Byte (TTFB). Using router.visit navigates the application effectively. However, an issue arises when reloading the page: the data fetch initiates correctly but the data itself fails to update in the UI.
Steps to reproduce:
When calling router.reload
in a useEffect
, we notice that the data is not updated correctly when reloading page using the browser.
// use-partial-reload.ts
import { useEffect } from 'react';
import { router } from '@inertiajs/react';
export default function usePartialReload(only: string[]) {
useEffect(() => {
router.reload({
only,
});
}, []);
}
// component.tsx
import React from 'react';
import { Head, usePage } from '@inertiajs/react';
import { usePartialReload } from '@/hooks';
export default function Index() {
usePartialReload(['contacts']);
// contacts stay undefined when reloading the page, but get hydrated when using `router.visit`
const { contacts } = usePage().props;
return (
<>
<Head title="Contacts" />
{contacts?.data.length}
</>
);
}
Unfortunately, this issue has already been raised, for example: https://github.com/inertiajs/inertia/issues/1547
The setTimeout(..., 0)
solution still works but something seems off about it.
Debug:
I've tried to do a little debugging and apply some logs to understand what's going on. When the Inertia application is created for the first time, the router.init
method is launched, followed by router.handleInitialPageVisit
. We can then notice that the setPage
method is called, in which the element of interest is this one:
https://github.com/inertiajs/inertia/blob/683155c8639aef8dc812c3fab4cdb5e2a35eccde/packages/core/src/router.ts#L469
So I started logging this condition, and indeed, on the first load the visitId
changes because the router.reload
is called before the router.handleInitialPageVisit
. So when we receive the router.reload
result, because of this condition, the result is ignored because the visitId
has been updated by router.handleInitialPageVisit
.
A possible solution?
I've never contributed to Inertia, so certainly this solution may have edge cases, but having a bit of fun with the sources I noticed that setPage
can take a visitId
as a parameter to avoid regenerating it.
So, if I detect that a visitId
has already been initialized before the handleInitialPageVisit
, I can simply reuse it to avoid regenerating it and ignore the router.reload
result.
diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts
--- packages/core/src/router.ts
+++ packages/core/src/router.ts
@@ -83,9 +83,9 @@
}
protected handleInitialPageVisit(page: Page): void {
this.page.url += window.location.hash
- this.setPage(page, { preserveState: true }).then(() => fireNavigateEvent(page))
+ this.setPage(page, { preserveState: true, visitId: this.visitId ?? undefined }).then(() => fireNavigateEvent(page))
}
protected setupEventListeners(): void {
window.addEventListener('popstate', this.handlePopstateEvent.bind(this))
View source: https://github.com/inertiajs/inertia/blob/683155c8639aef8dc812c3fab4cdb5e2a35eccde/packages/core/src/router.ts#L87
When using this patch, here is the result:
And the UI updates well, whether following a
router.visit
or a browser reload.
If this solution is suitable, I can make a PR with test cases if necessary. In the meantime, I'm keeping the setTimeout(..., 0)
which works for the moment. Also, many thanks for this library.