From 041126905d5fa16976d85838411a3bd3e8ec84a6 Mon Sep 17 00:00:00 2001 From: Nereo Berardozzi Date: Fri, 20 Jun 2025 15:24:47 +0200 Subject: [PATCH 1/2] Reload lazy props in WhenVisible component when they become undefined --- packages/react/src/WhenVisible.ts | 35 ++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/react/src/WhenVisible.ts b/packages/react/src/WhenVisible.ts index d24c65eb9..aeb337f78 100644 --- a/packages/react/src/WhenVisible.ts +++ b/packages/react/src/WhenVisible.ts @@ -1,5 +1,6 @@ import { ReloadOptions, router } from '@inertiajs/core' -import { createElement, ReactElement, useCallback, useEffect, useRef, useState } from 'react' +import { createElement, ReactElement, useCallback, useEffect, useMemo, useRef } from 'react' +import usePage from './usePage' interface WhenVisibleProps { children: ReactElement | number | string @@ -16,15 +17,29 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W as = as ?? 'div' fallback = fallback ?? null - const [loaded, setLoaded] = useState(false) + const loaded = useRef(false) const hasFetched = useRef(false) const fetching = useRef(false) const ref = useRef(null) + const pageProps = usePage().props + const keys = useMemo(() => (params ? (params.only ?? []) : Array.isArray(data) ? data : [data]), [data, params]) + + loaded.current = useMemo(() => { + if (fetching.current || !loaded.current) return loaded.current + + const propsLoaded = !keys.length ? true : keys.every((key) => pageProps[key] !== undefined) + + if (!propsLoaded) { + hasFetched.current = false + } + + return propsLoaded + }, [pageProps, keys]) const getReloadParams = useCallback<() => Partial>(() => { if (data) { return { - only: (Array.isArray(data) ? data : [data]) as string[], + only: keys, } } @@ -33,10 +48,10 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W } return params - }, [params, data]) + }, [params, data, keys]) useEffect(() => { - if (!ref.current) { + if (!ref.current || loaded.current) { return } @@ -66,7 +81,7 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W reloadParams.onStart?.(e) }, onFinish: (e) => { - setLoaded(true) + loaded.current = true fetching.current = false reloadParams.onFinish?.(e) @@ -86,20 +101,20 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W return () => { observer.disconnect() } - }, [ref, getReloadParams, buffer]) + }, [ref, getReloadParams, buffer, loaded.current]) - if (always || !loaded) { + if (always || !loaded.current) { return createElement( as, { props: null, ref, }, - loaded ? children : fallback, + loaded.current ? children : fallback, ) } - return loaded ? children : null + return loaded.current ? children : null } WhenVisible.displayName = 'InertiaWhenVisible' From 8203ebbe636d2889f880cf7024f0c23c683d9849 Mon Sep 17 00:00:00 2001 From: Nereo Berardozzi Date: Fri, 20 Jun 2025 15:52:03 +0200 Subject: [PATCH 2/2] Added WhenVisible reload test in react --- .../{WhenVisible.jsx => WhenVisible/Page.jsx} | 0 .../test-app/Pages/WhenVisible/WithReload.jsx | 26 ++++++++++++++ tests/app/server.js | 22 ++++++++++-- tests/when-visible.spec.ts | 34 ++++++++++++++++--- 4 files changed, 76 insertions(+), 6 deletions(-) rename packages/react/test-app/Pages/{WhenVisible.jsx => WhenVisible/Page.jsx} (100%) create mode 100644 packages/react/test-app/Pages/WhenVisible/WithReload.jsx diff --git a/packages/react/test-app/Pages/WhenVisible.jsx b/packages/react/test-app/Pages/WhenVisible/Page.jsx similarity index 100% rename from packages/react/test-app/Pages/WhenVisible.jsx rename to packages/react/test-app/Pages/WhenVisible/Page.jsx diff --git a/packages/react/test-app/Pages/WhenVisible/WithReload.jsx b/packages/react/test-app/Pages/WhenVisible/WithReload.jsx new file mode 100644 index 000000000..46ce8a525 --- /dev/null +++ b/packages/react/test-app/Pages/WhenVisible/WithReload.jsx @@ -0,0 +1,26 @@ +import { router } from '@inertiajs/core' +import { usePage, WhenVisible } from '@inertiajs/react' + +const Value = ({ value }) => { + return
{value.text}
+} + +export default () => { + const { foo, bar } = usePage().props + + const handleTriggerPageReload = () => { + router.reload() + } + + return ( +
+ Loading foo...
}> + + + Loading bar...} always> + + + + + ) +} diff --git a/tests/app/server.js b/tests/app/server.js index 8b82e6a1b..f29ff77c3 100644 --- a/tests/app/server.js +++ b/tests/app/server.js @@ -277,10 +277,11 @@ app.get('/history/version/:pageNumber', (req, res) => { }) }) -app.get('/when-visible', (req, res) => { +app.get('/when-visible/page', (req, res) => { const page = () => inertia.render(req, res, { - component: 'WhenVisible', + // TODO: move page on other packages + component: process.env.PACKAGE === 'react' ? 'WhenVisible/Page' : 'WhenVisible', props: {}, }) @@ -291,6 +292,23 @@ app.get('/when-visible', (req, res) => { } }) +app.get('/when-visible/with-reload', (req, res) => { + const page = () => + inertia.render(req, res, { + component: 'WhenVisible/WithReload', + props: { + foo: req.headers['x-inertia-partial-data']?.includes('foo') ? { text: 'foo is visible!' } : undefined, + bar: req.headers['x-inertia-partial-data']?.includes('bar') ? { text: 'bar is visible!' } : undefined, + }, + }) + + if (req.headers['x-inertia-partial-data']) { + setTimeout(page, 250) + } else { + page() + } +}) + app.get('/progress/:pageNumber', (req, res) => { setTimeout( () => diff --git a/tests/when-visible.spec.ts b/tests/when-visible.spec.ts index 5fb504392..af8241222 100644 --- a/tests/when-visible.spec.ts +++ b/tests/when-visible.spec.ts @@ -1,11 +1,9 @@ import { expect, test } from '@playwright/test' import { requests } from './support' -test.beforeEach(async ({ page }) => { - await page.goto('/when-visible') -}) - test('it will wait to fire the reload until element is visible', async ({ page }) => { + await page.goto('/when-visible/page') + requests.listen(page) await page.evaluate(() => (window as any).scrollTo(0, 1000)) @@ -85,3 +83,31 @@ test('it will wait to fire the reload until element is visible', async ({ page } await page.waitForResponse(page.url() + '?count=1') await expect(page.getByText('Count is now 2')).toBeVisible() }) + +test('it will reload the props when page reloads', async ({ page }) => { + // TODO: implement test for other packages + test.skip(process.env.PACKAGE !== 'react', 'React only test') + await page.goto('/when-visible/with-reload') + + requests.listen(page) + + await expect(page.getByText('Loading foo...')).toBeVisible() + await expect(page.getByText('Loading bar...')).toBeVisible() + await page.waitForResponse(page.url()) + await expect(page.getByText('Loading foo...')).not.toBeVisible() + await expect(page.getByText('Loading bar...')).not.toBeVisible() + await expect(page.getByText('foo is visible!')).toBeVisible() + await expect(page.getByText('bar is visible!')).toBeVisible() + + const responsePromise = page.waitForResponse(page.url()) + + console.log('clicking reload') + await page.getByRole('button', { exact: true, name: 'Trigger page reload' }).click() + await expect(page.getByText('Loading foo...')).toBeVisible() + await expect(page.getByText('Loading bar...')).toBeVisible() + + await responsePromise + + await expect(page.getByText('foo is visible!')).toBeVisible() + await expect(page.getByText('bar is visible!')).toBeVisible() +})