Skip to content

Commit

Permalink
Merge pull request #2153 from Loeffeldude/master
Browse files Browse the repository at this point in the history
 WhenVisible useEffect function is not recreated when params change. #2152
  • Loading branch information
joetannenbaum authored Jan 9, 2025
2 parents af56fba + 6b2101b commit e351805
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 40 deletions.
37 changes: 21 additions & 16 deletions packages/react/src/WhenVisible.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<IntersectionObserver | null>(null)
const hasFetched = useRef<boolean>(false)
const fetching = useRef<boolean>(false)
const ref = useRef<HTMLDivElement>(null)

const getReloadParams = (): Partial<ReloadOptions> => {
const getReloadParams = useCallback<() => Partial<ReloadOptions>>(() => {
if (data) {
return {
only: (Array.isArray(data) ? data : [data]) as string[],
Expand All @@ -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()
}
},
})
},
Expand All @@ -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(
Expand Down
66 changes: 44 additions & 22 deletions packages/react/test-app/Pages/WhenVisible.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
import { WhenVisible } from '@inertiajs/react'
import { useState } from 'react'

const Foo = ({ label }) => {
return <div>{label}</div>
}

export default () => (
<>
<div style={{ marginTop: '5000px' }}>
<WhenVisible data="foo" fallback={<div>Loading first one...</div>}>
<Foo label="First one is visible!" />
</WhenVisible>
</div>
export default () => {
const [count, setCount] = useState(0)

<div style={{ marginTop: '5000px' }}>
<WhenVisible buffer={1000} data="foo" fallback={<div>Loading second one...</div>}>
<Foo label="Second one is visible!" />
</WhenVisible>
</div>
return (
<>
<div style={{ marginTop: '5000px' }}>
<WhenVisible data="foo" fallback={<div>Loading first one...</div>}>
<Foo label="First one is visible!" />
</WhenVisible>
</div>

<div style={{ marginTop: '5000px' }}>
<WhenVisible data="foo" fallback={<div>Loading third one...</div>} always>
<Foo label="Third one is visible!" />
</WhenVisible>
</div>
<div style={{ marginTop: '5000px' }}>
<WhenVisible buffer={1000} data="foo" fallback={<div>Loading second one...</div>}>
<Foo label="Second one is visible!" />
</WhenVisible>
</div>

<div style={{ marginTop: '5000px' }}>
<WhenVisible data="foo" fallback={<div>Loading fourth one...</div>}></WhenVisible>
</div>
</>
)
<div style={{ marginTop: '5000px' }}>
<WhenVisible data="foo" fallback={<div>Loading third one...</div>} always>
<Foo label="Third one is visible!" />
</WhenVisible>
</div>

<div style={{ marginTop: '5000px' }}>
<WhenVisible data="foo" fallback={<div>Loading fourth one...</div>}></WhenVisible>
</div>

<div style={{ marginTop: '6000px' }}>
<WhenVisible
fallback={<div>Loading fifth one...</div>}
always
params={{
data: {
count,
},
onSuccess: () => {
setCount((c) => c + 1)
},
}}
>
<Foo label={`Count is now ${count}`} />
</WhenVisible>
</div>
</>
)
}
19 changes: 19 additions & 0 deletions packages/svelte/test-app/Pages/WhenVisible.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script>
import { WhenVisible } from '@inertiajs/svelte'
export let count = 0
</script>

<div style="margin-top: 5000px">
Expand Down Expand Up @@ -39,3 +41,20 @@
</svelte:fragment>
</WhenVisible>
</div>

<div style="margin-top: 6000px">
<WhenVisible always params={{
data: {
count,
},
onSuccess() {
count += 1
}
}}>
<svelte:fragment slot="fallback">
<div>Loading fifth one...</div>
</svelte:fragment>

<div>Count is now {count}</div>
</WhenVisible>
</div>
23 changes: 23 additions & 0 deletions packages/vue3/test-app/Pages/WhenVisible.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script setup lang="ts">
import { WhenVisible } from '@inertiajs/vue3'
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
Expand Down Expand Up @@ -40,4 +43,24 @@ import { WhenVisible } from '@inertiajs/vue3'
</template>
</WhenVisible>
</div>

<div style="margin-top: 6000px">
<WhenVisible
always
:params="{
data: {
count,
},
onSuccess: () => {
count += 1
},
}"
>
<template #fallback>
<div>Loading fifth one...</div>
</template>

<div>Count is now {{ count }}</div>
</WhenVisible>
</div>
</template>
4 changes: 2 additions & 2 deletions tests/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
15 changes: 15 additions & 0 deletions tests/when-visible.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})

0 comments on commit e351805

Please sign in to comment.