diff --git a/packages/react/src/WhenVisible.ts b/packages/react/src/WhenVisible.ts index 590366569..d24c65eb9 100644 --- a/packages/react/src/WhenVisible.ts +++ b/packages/react/src/WhenVisible.ts @@ -1,10 +1,10 @@ import { ReloadOptions, router } from '@inertiajs/core' -import { createElement, ReactElement, useEffect, useRef, useState } from 'react' +import { createElement, ReactElement, useCallback, useEffect, useRef, useState } from 'react' interface WhenVisibleProps { children: ReactElement | number | string fallback: ReactElement | number | string - data: string | string[] + data?: string | string[] params?: ReloadOptions buffer?: number as?: string @@ -17,11 +17,11 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W fallback = fallback ?? null const [loaded, setLoaded] = useState(false) - const [fetching, setFetching] = useState(false) - const observer = useRef(null) + const hasFetched = useRef(false) + const fetching = useRef(false) const ref = useRef(null) - const getReloadParams = (): Partial => { + const getReloadParams = useCallback<() => Partial>(() => { if (data) { return { only: (Array.isArray(data) ? data : [data]) as string[], @@ -33,41 +33,46 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W } return params - } + }, [params, data]) useEffect(() => { if (!ref.current) { return } - observer.current = new IntersectionObserver( + const observer = new IntersectionObserver( (entries) => { if (!entries[0].isIntersecting) { return } - if (!always) { - observer.current?.disconnect() + if (!always && hasFetched.current) { + observer.disconnect() } - if (fetching) { + if (fetching.current) { return } - setFetching(true) + hasFetched.current = true + fetching.current = true const reloadParams = getReloadParams() router.reload({ ...reloadParams, onStart: (e) => { - setFetching(true) + fetching.current = true reloadParams.onStart?.(e) }, onFinish: (e) => { setLoaded(true) - setFetching(false) + fetching.current = false reloadParams.onFinish?.(e) + + if (!always) { + observer.disconnect() + } }, }) }, @@ -76,12 +81,12 @@ const WhenVisible = ({ children, data, params, buffer, as, always, fallback }: W }, ) - observer.current.observe(ref.current) + observer.observe(ref.current) return () => { - observer.current?.disconnect() + observer.disconnect() } - }, [ref]) + }, [ref, getReloadParams, buffer]) if (always || !loaded) { return createElement( diff --git a/packages/react/test-app/Pages/WhenVisible.jsx b/packages/react/test-app/Pages/WhenVisible.jsx index e0dbb3719..ed946b0a4 100644 --- a/packages/react/test-app/Pages/WhenVisible.jsx +++ b/packages/react/test-app/Pages/WhenVisible.jsx @@ -1,31 +1,53 @@ import { WhenVisible } from '@inertiajs/react' +import { useState } from 'react' const Foo = ({ label }) => { return
{label}
} -export default () => ( - <> -
- Loading first one...
}> - - - +export default () => { + const [count, setCount] = useState(0) -
- Loading second one...
}> - - - + return ( + <> +
+ Loading first one...
}> + + + -
- Loading third one...
} always> - - - +
+ Loading second one...
}> + + + -
- Loading fourth one...
}> - - -) +
+ Loading third one...
} always> + + + + +
+ Loading fourth one...
}> + + +
+ Loading fifth one...
} + always + params={{ + data: { + count, + }, + onSuccess: () => { + setCount((c) => c + 1) + }, + }} + > + + + + + ) +} diff --git a/packages/svelte/test-app/Pages/WhenVisible.svelte b/packages/svelte/test-app/Pages/WhenVisible.svelte index 1e192b8dd..116158c4b 100644 --- a/packages/svelte/test-app/Pages/WhenVisible.svelte +++ b/packages/svelte/test-app/Pages/WhenVisible.svelte @@ -1,5 +1,7 @@
@@ -39,3 +41,20 @@
+ +
+ + +
Loading fifth one...
+
+ +
Count is now {count}
+
+
diff --git a/packages/vue3/test-app/Pages/WhenVisible.vue b/packages/vue3/test-app/Pages/WhenVisible.vue index c5482dbf5..3daa3611c 100644 --- a/packages/vue3/test-app/Pages/WhenVisible.vue +++ b/packages/vue3/test-app/Pages/WhenVisible.vue @@ -1,5 +1,8 @@ + +
+ + + +
Count is now {{ count }}
+
+
diff --git a/tests/app/server.js b/tests/app/server.js index 91b69610e..f68696c71 100644 --- a/tests/app/server.js +++ b/tests/app/server.js @@ -237,8 +237,8 @@ app.get('/when-visible', (req, res) => { props: {}, }) - if (req.headers['x-inertia-partial-data']) { - setTimeout(page, 500) + if (req.headers['x-inertia-partial-data'] || req.query.count) { + setTimeout(page, 250) } else { page() } diff --git a/tests/when-visible.spec.ts b/tests/when-visible.spec.ts index 565b0c6c1..5fb504392 100644 --- a/tests/when-visible.spec.ts +++ b/tests/when-visible.spec.ts @@ -69,4 +69,19 @@ test('it will wait to fire the reload until element is visible', async ({ page } await expect(page.getByText('Loading fourth one...')).toBeVisible() await page.waitForResponse(page.url()) await expect(page.getByText('Loading fourth one...')).not.toBeVisible() + + await page.evaluate(() => (window as any).scrollTo(0, 26_000)) + await expect(page.getByText('Loading fifth one...')).toBeVisible() + await page.waitForResponse(page.url() + '?count=0') + await expect(page.getByText('Loading fifth one...')).not.toBeVisible() + await expect(page.getByText('Count is now 1')).toBeVisible() + + // Now scroll up and down to re-trigger it + await page.evaluate(() => (window as any).scrollTo(0, 20_000)) + await page.waitForTimeout(100) + + await page.evaluate(() => (window as any).scrollTo(0, 26_000)) + await expect(page.getByText('Count is now 1')).toBeVisible() + await page.waitForResponse(page.url() + '?count=1') + await expect(page.getByText('Count is now 2')).toBeVisible() })