From b0dfc82b83b1e6b8a5c9a18f2a1232756631a257 Mon Sep 17 00:00:00 2001 From: Santiago Cajal Date: Wed, 29 Oct 2025 11:37:40 -0300 Subject: [PATCH 1/4] Refactor `Deferred` component to accept loading state in children callback and make fallback optional. --- packages/react/src/Deferred.ts | 10 +++---- .../Pages/DeferredProps/WithCallback.tsx | 26 +++++++++++++++++++ .../react/resources/js/Pages/Defer.tsx | 6 ++--- tests/app/server.js | 24 +++++++++++++++++ tests/deferred-props.spec.ts | 14 ++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 packages/react/test-app/Pages/DeferredProps/WithCallback.tsx diff --git a/packages/react/src/Deferred.ts b/packages/react/src/Deferred.ts index 4cb09551b..3debab455 100644 --- a/packages/react/src/Deferred.ts +++ b/packages/react/src/Deferred.ts @@ -14,8 +14,8 @@ const isSameUrlWithoutHash = (url1: URL | Location, url2: URL | Location): boole } interface DeferredProps { - children: ReactNode | (() => ReactNode) - fallback: ReactNode | (() => ReactNode) + children: ReactNode | ((props: {loading: boolean}) => ReactNode) + fallback?: ReactNode | (() => ReactNode) data: string | string[] } @@ -47,11 +47,11 @@ const Deferred = ({ children, data, fallback }: DeferredProps) => { setLoaded(keys.every((key) => pageProps[key] !== undefined)) }, [pageProps, keys]) - if (loaded) { - return typeof children === 'function' ? children() : children + if (! loaded && fallback) { + return typeof fallback === 'function' ? fallback() : fallback } - return typeof fallback === 'function' ? fallback() : fallback + return typeof children === 'function' ? children({ loading: !loaded }) : children } Deferred.displayName = 'InertiaDeferred' diff --git a/packages/react/test-app/Pages/DeferredProps/WithCallback.tsx b/packages/react/test-app/Pages/DeferredProps/WithCallback.tsx new file mode 100644 index 000000000..d944f97a6 --- /dev/null +++ b/packages/react/test-app/Pages/DeferredProps/WithCallback.tsx @@ -0,0 +1,26 @@ +import { Deferred, Link, usePage } from '@inertiajs/react' + +const Foo = () => { + const { foo } = usePage<{ foo?: { text: string } }>().props + + return foo?.text +} + +const Bar = () => { + const { bar } = usePage<{ bar?: { text: string } }>().props + + return bar?.text +} + +export default () => { + return ( + <> + {({ loading }) => (loading ?
Loading foo (callback)...
: )}
+ + {({ loading }) => (loading ?
Loading bar (callback)...
: )}
+ + Page 1 + Page 2 + + ) +} diff --git a/playgrounds/react/resources/js/Pages/Defer.tsx b/playgrounds/react/resources/js/Pages/Defer.tsx index 4e01b2a3e..8a0bbe3f2 100644 --- a/playgrounds/react/resources/js/Pages/Defer.tsx +++ b/playgrounds/react/resources/js/Pages/Defer.tsx @@ -38,9 +38,9 @@ const Defer = ({
- {/* Loading Food...

}> - -
*/} + + {({ loading }) => (loading ?

Loading Users (with callback)...

: )} +
diff --git a/tests/app/server.js b/tests/app/server.js index f9238a104..7307657ef 100644 --- a/tests/app/server.js +++ b/tests/app/server.js @@ -634,6 +634,30 @@ app.get('/deferred-props/page-1', (req, res) => { ) }) +app.get('/deferred-props/with-callback', (req, res) => { + if (!req.headers['x-inertia-partial-data']) { + return inertia.render(req, res, { + component: 'DeferredProps/WithCallback', + deferredProps: { + default: ['foo', 'bar'], + }, + props: {}, + }) + } + + setTimeout( + () => + inertia.render(req, res, { + component: 'DeferredProps/WithCallback', + props: { + foo: req.headers['x-inertia-partial-data']?.includes('foo') ? { text: 'foo value' } : undefined, + bar: req.headers['x-inertia-partial-data']?.includes('bar') ? { text: 'bar value' } : undefined, + }, + }), + 500, + ) +}) + app.get('/deferred-props/with-partial-reload/:mode', (req, res) => { if (!req.headers['x-inertia-partial-data']) { return inertia.render(req, res, { diff --git a/tests/deferred-props.spec.ts b/tests/deferred-props.spec.ts index c6c7c6a56..3002e4d1d 100644 --- a/tests/deferred-props.spec.ts +++ b/tests/deferred-props.spec.ts @@ -197,3 +197,17 @@ test('load deferred props with partial reload on mount', async ({ page }) => { await expect(page.getByText('foo value')).toBeVisible() await expect(page.getByText('bar value')).toBeVisible() }) + +test('load deferred props with callback', async ({ page }) => { + await page.goto('/deferred-props/with-callback') + + await expect(page.getByText('Loading foo (callback)...')).toBeVisible() + await expect(page.getByText('Loading bar (callback)...')).toBeVisible() + + await page.waitForResponse(page.url()) + + await expect(page.getByText('Loading foo (callback)...')).not.toBeVisible() + await expect(page.getByText('Loading bar (callback)...')).not.toBeVisible() + await expect(page.getByText('foo value')).toBeVisible() + await expect(page.getByText('bar value')).toBeVisible() +}) \ No newline at end of file From 0f95c9cae2137326998d3c8149653a1b267bdafc Mon Sep 17 00:00:00 2001 From: Santiago Cajal Date: Wed, 29 Oct 2025 13:55:13 -0300 Subject: [PATCH 2/4] vue implementation --- packages/vue3/src/deferred.ts | 7 ++--- .../Pages/DeferredProps/WithCallback.vue | 27 +++++++++++++++++++ playgrounds/vue3/resources/js/Pages/Defer.vue | 11 ++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 packages/vue3/test-app/Pages/DeferredProps/WithCallback.vue diff --git a/packages/vue3/src/deferred.ts b/packages/vue3/src/deferred.ts index 9557d5a33..fd1bb529e 100644 --- a/packages/vue3/src/deferred.ts +++ b/packages/vue3/src/deferred.ts @@ -10,11 +10,12 @@ export default defineComponent({ }, render() { const keys = (Array.isArray(this.$props.data) ? this.$props.data : [this.$props.data]) as string[] + const loaded = keys.every((key) => this.$page.props[key] !== undefined) - if (!this.$slots.fallback) { - throw new Error('`` requires a `