Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions packages/react/src/WhenVisible.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<boolean>(false)
const hasFetched = useRef<boolean>(false)
const fetching = useRef<boolean>(false)
const ref = useRef<HTMLDivElement>(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<ReloadOptions>>(() => {
if (data) {
return {
only: (Array.isArray(data) ? data : [data]) as string[],
only: keys,
}
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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)

Expand All @@ -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'
Expand Down
26 changes: 26 additions & 0 deletions packages/react/test-app/Pages/WhenVisible/WithReload.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { router } from '@inertiajs/core'
import { usePage, WhenVisible } from '@inertiajs/react'

const Value = ({ value }) => {
return <div>{value.text}</div>
}

export default () => {
const { foo, bar } = usePage().props

const handleTriggerPageReload = () => {
router.reload()
}

return (
<div>
<WhenVisible data="foo" fallback={<div>Loading foo...</div>}>
<Value value={foo} />
</WhenVisible>
<WhenVisible data="bar" fallback={<div>Loading bar...</div>} always>
<Value value={bar} />
</WhenVisible>
<button onClick={handleTriggerPageReload}>Trigger page reload</button>
</div>
)
}
22 changes: 20 additions & 2 deletions tests/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
})

Expand All @@ -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(
() =>
Expand Down
34 changes: 30 additions & 4 deletions tests/when-visible.spec.ts
Original file line number Diff line number Diff line change
@@ -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))
Expand Down Expand Up @@ -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()
})
Loading