Defer artwork comments from initial render

This commit is contained in:
2026-08-29 12:07:08 +02:00
parent 8a80aae21e
commit 4ab315e9d7
8 changed files with 267 additions and 138 deletions
@@ -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>
)
}