diff --git a/packages/react/src/Deferred.ts b/packages/react/src/Deferred.ts index 4d2cb4fe8..4be3369aa 100644 --- a/packages/react/src/Deferred.ts +++ b/packages/react/src/Deferred.ts @@ -1,4 +1,5 @@ -import { ReactElement, useEffect, useState } from 'react' +import { ReactElement, useEffect, useMemo, useState } from 'react' +import { router } from '.' import usePage from './usePage' interface DeferredProps { @@ -14,7 +15,22 @@ const Deferred = ({ children, data, fallback }: DeferredProps) => { const [loaded, setLoaded] = useState(false) const pageProps = usePage().props - const keys = Array.isArray(data) ? data : [data] + const keys = useMemo(() => (Array.isArray(data) ? data : [data]), [data]) + + useEffect(() => { + const removeListener = router.on('start', (e) => { + if ( + (e.detail.visit.only.length === 0 && e.detail.visit.except.length === 0) || + e.detail.visit.only.find((key) => keys.includes(key)) + ) { + setLoaded(false) + } + }) + + return () => { + removeListener() + } + }, []) useEffect(() => { setLoaded(keys.every((key) => pageProps[key] !== undefined)) diff --git a/packages/react/test-app/Pages/DeferredProps/WithPartialReload.jsx b/packages/react/test-app/Pages/DeferredProps/WithPartialReload.jsx new file mode 100644 index 000000000..396ad5a2d --- /dev/null +++ b/packages/react/test-app/Pages/DeferredProps/WithPartialReload.jsx @@ -0,0 +1,33 @@ +import { Deferred, router, usePage } from '@inertiajs/react' + +const WithPartialReload = ({ withOnly, withExcept }) => { + const handleTriggerPartialReload = () => { + router.reload({ + only: withOnly, + except: withExcept, + }) + } + + return ( +
+ Loading...}> + + + +
+ ) +} + +const DeferredUsers = () => { + const props = usePage().props + + return ( +
+ {props.users.map((user) => ( + {user.name} + ))} +
+ ) +} + +export default WithPartialReload diff --git a/tests/app/server.js b/tests/app/server.js index ab0f47f26..4ac1d2b1f 100644 --- a/tests/app/server.js +++ b/tests/app/server.js @@ -293,6 +293,52 @@ app.get('/deferred-props/page-1', (req, res) => { ) }) +app.get('/deferred-props/with-partial-reload/:mode', (req, res) => { + if (!req.headers['x-inertia-partial-data']) { + return inertia.render(req, res, { + component: 'DeferredProps/WithPartialReload', + deferredProps: { + default: ['users'], + }, + props: { + withOnly: (() => { + if (req.params.mode === 'only') { + return ['users'] + } + + if (req.params.mode === 'only-other') { + return ['other'] + } + + return [] + })(), + withExcept: (() => { + if (req.params.mode === 'except') { + return ['users'] + } + + if (req.params.mode === 'except-other') { + return ['other'] + } + + return [] + })(), + }, + }) + } + + setTimeout( + () => + inertia.render(req, res, { + component: 'DeferredProps/WithPartialReload', + props: { + users: req.headers['x-inertia-partial-data']?.includes('users') ? [{ id: 1, name: 'John Doe' }] : undefined, + }, + }), + 500, + ) +}) + app.get('/deferred-props/page-2', (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 2b41f2f79..7437de6bf 100644 --- a/tests/deferred-props.spec.ts +++ b/tests/deferred-props.spec.ts @@ -88,3 +88,54 @@ test('props will re-defer if a link is clicked to go to the same page again', as await expect(page.getByText('foo value')).toBeVisible() await expect(page.getByText('bar value')).toBeVisible() }) + +const shoulReload = ['only'] + +shoulReload.forEach((type) => { + test(`it will handle partial reloads properly when deferred is being reloaded (${type})`, async ({ page }) => { + test.skip(process.env.PACKAGE !== 'react', 'React only test') + + await page.goto(`/deferred-props/with-partial-reload/${type}`) + + await expect(page.getByText('Loading...')).toBeVisible() + + await page.waitForResponse(page.url()) + + await expect(page.getByText('Loading...')).not.toBeVisible() + await expect(page.getByText('John Doe')).toBeVisible() + + const responsePromise = page.waitForResponse(page.url()) + + await page.getByRole('button', { exact: true, name: 'Trigger a partial reload' }).click() + await expect(page.getByText('Loading...')).toBeVisible() + + await responsePromise + + await expect(page.getByText('John Doe')).toBeVisible() + }) +}) + +const noReload = ['except', 'only-other', 'none', 'except-other'] + +noReload.forEach((type) => { + test(`it will handle partial reloads properly when deferred is not reloaded (${type})`, async ({ page }) => { + test.skip(process.env.PACKAGE !== 'react', 'React only test') + + await page.goto(`/deferred-props/with-partial-reload/${type}`) + + await expect(page.getByText('Loading...')).toBeVisible() + + await page.waitForResponse(page.url()) + + await expect(page.getByText('Loading...')).not.toBeVisible() + await expect(page.getByText('John Doe')).toBeVisible() + + const responsePromise = page.waitForResponse(page.url()) + await page.getByRole('button', { exact: true, name: 'Trigger a partial reload' }).click() + await expect(page.getByText('Loading...')).not.toBeVisible() + + await responsePromise + + await expect(page.getByText('John Doe')).toBeVisible() + }) +})