Skip to content

Commit 30453c4

Browse files
committed
react when visible
1 parent 84c5402 commit 30453c4

File tree

3 files changed

+114
-13
lines changed

3 files changed

+114
-13
lines changed

packages/react/src/WhenVisible.ts

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { ReloadOptions, router } from '@inertiajs/core'
2+
import { createElement, ReactChild, useEffect, useRef, useState } from 'react'
3+
4+
interface WhenVisibleProps {
5+
children: ReactChild
6+
fallback: ReactChild
7+
data: string | string[]
8+
params?: ReloadOptions
9+
buffer?: number
10+
elementTag?: string
11+
once?: boolean
12+
}
13+
14+
const WhenVisible = ({ children, data, params, buffer, elementTag, once, fallback }: WhenVisibleProps) => {
15+
once = once ?? true
16+
elementTag = elementTag ?? 'div'
17+
fallback = fallback ?? null
18+
19+
const [loaded, setLoaded] = useState(false)
20+
const [fetching, setFetching] = useState(false)
21+
const observer = useRef<IntersectionObserver | null>(null)
22+
const ref = useRef<HTMLDivElement>(null)
23+
24+
const getReloadParams = (): Partial<ReloadOptions> => {
25+
if (data) {
26+
return {
27+
only: (Array.isArray(data) ? data : [data]) as string[],
28+
}
29+
}
30+
31+
if (!params) {
32+
throw new Error('You must provide either a `data` or `params` prop.')
33+
}
34+
35+
return params
36+
}
37+
38+
useEffect(() => {
39+
if (!ref.current) {
40+
return
41+
}
42+
43+
observer.current = new IntersectionObserver(
44+
(entries) => {
45+
if (!entries[0].isIntersecting) {
46+
return
47+
}
48+
49+
if (once) {
50+
observer.current?.disconnect()
51+
}
52+
53+
if (fetching) {
54+
return
55+
}
56+
57+
setFetching(true)
58+
59+
const reloadParams = getReloadParams()
60+
61+
router.reload({
62+
...reloadParams,
63+
onStart: (e) => {
64+
setFetching(true)
65+
reloadParams.onStart?.(e)
66+
},
67+
onFinish: (e) => {
68+
setLoaded(true)
69+
setFetching(false)
70+
reloadParams.onFinish?.(e)
71+
},
72+
})
73+
},
74+
{
75+
rootMargin: `${buffer || 0}px`,
76+
},
77+
)
78+
79+
observer.current.observe(ref.current)
80+
81+
return () => {
82+
observer.current?.disconnect()
83+
}
84+
}, [ref])
85+
86+
if (!once || !loaded) {
87+
return createElement(
88+
elementTag,
89+
{
90+
props: null,
91+
ref,
92+
},
93+
loaded ? children : fallback,
94+
)
95+
}
96+
97+
return loaded ? children : null
98+
}
99+
100+
WhenVisible.displayName = 'InertiaWhenVisible'
101+
102+
export default WhenVisible

packages/react/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { default as useForm } from './useForm'
99
export { default as usePage } from './usePage'
1010
export { default as usePoll } from './usePoll'
1111
export { default as useRemember } from './useRemember'
12+
export { default as WhenVisible } from './WhenVisible'
+11-13
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
1-
import { Suspense } from 'react'
2-
3-
const Foo = ({ label, once }) => {
4-
const { foo } = useWhenVisible('foo')
1+
import { WhenVisible } from '@inertiajs/react'
52

3+
const Foo = ({ label }) => {
64
return <div>{label}</div>
75
}
86

97
export default () => (
108
<>
11-
<div style="margin-top: 5000px">
12-
<Suspense fallback={<div>Loading first one...</div>}>
9+
<div style={{ marginTop: '5000px' }}>
10+
<WhenVisible data="foo" fallback={<div>Loading first one...</div>}>
1311
<Foo label="First one is visible!" />
14-
</Suspense>
12+
</WhenVisible>
1513
</div>
1614

17-
<div style="margin-top: 5000px">
18-
<Suspense fallback={<div>Loading second one...</div>}>
15+
<div style={{ marginTop: '5000px' }}>
16+
<WhenVisible buffer={1000} data="foo" fallback={<div>Loading second one...</div>}>
1917
<Foo label="Second one is visible!" />
20-
</Suspense>
18+
</WhenVisible>
2119
</div>
2220

23-
<div style="margin-top: 5000px">
24-
<Suspense fallback={<div>Loading third one...</div>}>
21+
<div style={{ marginTop: '5000px' }}>
22+
<WhenVisible data="foo" once={false} fallback={<div>Loading third one...</div>}>
2523
<Foo label="Third one is visible!" once={false} />
26-
</Suspense>
24+
</WhenVisible>
2725
</div>
2826
</>
2927
)

0 commit comments

Comments
 (0)