Defer artwork comments from initial render
This commit is contained in:
@@ -466,23 +466,30 @@ function Skeleton() {
|
||||
|
||||
export default function ArtworkComments({
|
||||
artworkId,
|
||||
comments: initialComments = [],
|
||||
isLoggedIn = false,
|
||||
loginUrl = '/login',
|
||||
}) {
|
||||
const [comments, setComments] = useState(initialComments)
|
||||
const [comments, setComments] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [lastPage, setLastPage] = useState(1)
|
||||
const [total, setTotal] = useState(initialComments.length)
|
||||
const initialized = useRef(false)
|
||||
const [total, setTotal] = useState(0)
|
||||
const sectionRef = useRef(null)
|
||||
const requestRef = useRef(null)
|
||||
const generationRef = useRef(0)
|
||||
|
||||
const loadComments = useCallback(
|
||||
async (p = 1) => {
|
||||
if (!artworkId) return
|
||||
if (!artworkId || requestRef.current) return
|
||||
const generation = generationRef.current
|
||||
const controller = new AbortController()
|
||||
requestRef.current = controller
|
||||
setLoading(true)
|
||||
setError(false)
|
||||
try {
|
||||
const { data } = await axios.get(`/api/artworks/${artworkId}/comments?page=${p}`)
|
||||
const { data } = await axios.get(`/api/artworks/${artworkId}/comments?page=${p}`, { signal: controller.signal })
|
||||
if (generation !== generationRef.current) return
|
||||
if (p === 1) {
|
||||
setComments(data.data ?? [])
|
||||
} else {
|
||||
@@ -492,24 +499,62 @@ export default function ArtworkComments({
|
||||
setLastPage(data.meta?.last_page ?? 1)
|
||||
setTotal(data.meta?.total ?? 0)
|
||||
} catch {
|
||||
// keep existing
|
||||
if (generation === generationRef.current && !controller.signal.aborted) setError(true)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
if (requestRef.current === controller) {
|
||||
requestRef.current = null
|
||||
if (generation === generationRef.current) setLoading(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
[artworkId],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialized.current) return
|
||||
initialized.current = true
|
||||
generationRef.current += 1
|
||||
requestRef.current?.abort()
|
||||
requestRef.current = null
|
||||
setComments([])
|
||||
setLoading(false)
|
||||
setError(false)
|
||||
setPage(1)
|
||||
setLastPage(1)
|
||||
setTotal(0)
|
||||
|
||||
if (artworkId && initialComments.length === 0) {
|
||||
loadComments(1)
|
||||
} else {
|
||||
setTotal(initialComments.length)
|
||||
const section = sectionRef.current
|
||||
if (!section || !artworkId) return undefined
|
||||
|
||||
let fallbackTimer = null
|
||||
let observer = null
|
||||
let established = false
|
||||
const trigger = () => {
|
||||
observer?.disconnect()
|
||||
if (!requestRef.current) loadComments(1)
|
||||
}
|
||||
}, [artworkId, initialComments.length, loadComments])
|
||||
|
||||
if (typeof window !== 'undefined' && typeof window.IntersectionObserver === 'function') {
|
||||
try {
|
||||
observer = new window.IntersectionObserver((entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting)) trigger()
|
||||
}, { rootMargin: '1000px 0px' })
|
||||
observer.observe(section)
|
||||
established = true
|
||||
} catch {
|
||||
observer?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
if (!established && typeof window !== 'undefined') {
|
||||
fallbackTimer = window.setTimeout(trigger, 1200)
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer?.disconnect()
|
||||
if (fallbackTimer !== null && typeof window !== 'undefined') window.clearTimeout(fallbackTimer)
|
||||
requestRef.current?.abort()
|
||||
requestRef.current = null
|
||||
}
|
||||
}, [artworkId, loadComments])
|
||||
|
||||
// New top-level comment posted
|
||||
const handlePosted = useCallback((newComment) => {
|
||||
@@ -538,7 +583,7 @@ export default function ArtworkComments({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section aria-label="Comments" className="space-y-6">
|
||||
<section ref={sectionRef} aria-label="Comments" className="space-y-6">
|
||||
{/* Section header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-lg font-semibold tracking-tight text-white sm:text-xl">
|
||||
@@ -554,6 +599,11 @@ export default function ArtworkComments({
|
||||
{/* Comment list */}
|
||||
{loading && comments.length === 0 ? (
|
||||
<Skeleton />
|
||||
) : error && comments.length === 0 ? (
|
||||
<div className="rounded-2xl border border-dashed border-white/[0.08] bg-white/[0.015] px-6 py-8 text-center">
|
||||
<p className="text-sm font-medium text-white/45">Comments could not be loaded.</p>
|
||||
<button type="button" onClick={() => loadComments(1)} className="mt-3 rounded-full border border-white/[0.1] px-4 py-2 text-xs font-medium text-white/65 hover:bg-white/[0.06]">Try again</button>
|
||||
</div>
|
||||
) : comments.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-dashed border-white/[0.08] bg-white/[0.015] px-6 py-10 text-center">
|
||||
<ChatBubbleIcon />
|
||||
@@ -611,4 +661,3 @@ export default function ArtworkComments({
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user