perf: Lazy loading for links in topic sections (#127)

This commit is contained in:
Anselm Eickhoff
2024-09-03 11:27:17 +01:00
committed by GitHub
parent c4d4afd4df
commit d2a3baa9a2
7 changed files with 74 additions and 33 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -11,7 +11,7 @@
"web" "web"
], ],
"dependencies": { "dependencies": {
"jazz-nodejs": "^0.7.34", "jazz-nodejs": "0.7.35-unique.2",
"react-icons": "^5.3.0" "react-icons": "^5.3.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import React from "react" import React, { useRef } from "react"
import { TopicDetailHeader } from "./Header" import { TopicDetailHeader } from "./Header"
import { TopicSections } from "./partials/topic-sections" import { TopicSections } from "./partials/topic-sections"
import { useLinkNavigation } from "./use-link-navigation" import { useLinkNavigation } from "./use-link-navigation"
@@ -16,8 +16,10 @@ export const openPopoverForIdAtom = atom<string | null>(null)
export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) { export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) {
const { me } = useAccount({ root: { personalLinks: [] } }) const { me } = useAccount({ root: { personalLinks: [] } })
const { topic, allLinks } = useTopicData(topicName) const { topic } = useTopicData(topicName, me)
const { activeIndex, setActiveIndex, containerRef, linkRefs } = useLinkNavigation(allLinks) // const { activeIndex, setActiveIndex, containerRef, linkRefs } = useLinkNavigation(allLinks)
const linksRefDummy = useRef<(HTMLLIElement | null)[]>([])
const containerRefDummy = useRef<HTMLDivElement>(null)
if (!topic || !me) { if (!topic || !me) {
return null return null
@@ -29,10 +31,10 @@ export function TopicDetailRoute({ topicName }: TopicDetailRouteProps) {
<TopicSections <TopicSections
topic={topic} topic={topic}
sections={topic.latestGlobalGuide?.sections} sections={topic.latestGlobalGuide?.sections}
activeIndex={activeIndex} activeIndex={0}
setActiveIndex={setActiveIndex} setActiveIndex={() => {}}
linkRefs={linkRefs} linkRefs={linksRefDummy}
containerRef={containerRef} containerRef={containerRefDummy}
me={me} me={me}
personalLinks={me.root.personalLinks} personalLinks={me.root.personalLinks}
/> />

View File

@@ -1,6 +1,8 @@
import React from "react" import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { LinkItem } from "./link-item" import { LinkItem } from "./link-item"
import { LaAccount, PersonalLinkLists, Section as SectionSchema, Topic, UserRoot } from "@/lib/schema" import { LaAccount, PersonalLinkLists, Section as SectionSchema, Topic, UserRoot } from "@/lib/schema"
import { Skeleton } from "@/components/ui/skeleton"
import { Loader2 } from "lucide-react"
interface SectionProps { interface SectionProps {
topic: Topic topic: Topic
@@ -27,6 +29,12 @@ export function Section({
me, me,
personalLinks personalLinks
}: SectionProps) { }: SectionProps) {
const [nLinksToLoad, setNLinksToLoad] = useState(10);
const linksToLoad = useMemo(() => {
return section.links?.slice(0, nLinksToLoad)
}, [section.links, nLinksToLoad])
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex items-center gap-4 px-6 py-2 max-lg:px-4"> <div className="flex items-center gap-4 px-6 py-2 max-lg:px-4">
@@ -35,9 +43,9 @@ export function Section({
</div> </div>
<div className="flex flex-col gap-px py-2"> <div className="flex flex-col gap-px py-2">
{section.links?.map( {linksToLoad?.map(
(link, index) => (link, index) =>
link?.url && ( link?.url ? (
<LinkItem <LinkItem
key={index} key={index}
topic={topic} topic={topic}
@@ -51,9 +59,52 @@ export function Section({
me={me} me={me}
personalLinks={personalLinks} personalLinks={personalLinks}
/> />
) : (
<Skeleton key={index} className="h-14 xl:h-11 w-full" />
) )
)} )}
{section.links?.length && section.links?.length > nLinksToLoad && (
<LoadMoreSpinner onLoadMore={() => setNLinksToLoad(n => n + 10)} />
)}
</div> </div>
</div> </div>
) )
} }
const LoadMoreSpinner = ({ onLoadMore }: { onLoadMore: () => void }) => {
const spinnerRef = useRef<HTMLDivElement>(null)
const handleIntersection = useCallback(
(entries: IntersectionObserverEntry[]) => {
const [entry] = entries
if (entry.isIntersecting) {
onLoadMore()
}
},
[onLoadMore]
)
useEffect(() => {
const observer = new IntersectionObserver(handleIntersection, {
root: null,
rootMargin: "0px",
threshold: 1.0,
})
if (spinnerRef.current) {
observer.observe(spinnerRef.current)
}
return () => {
if (spinnerRef.current) {
observer.unobserve(spinnerRef.current)
}
}
}, [handleIntersection])
return (
<div ref={spinnerRef} className="flex justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
)
}

View File

@@ -1,29 +1,15 @@
import { useMemo } from "react" import { useMemo } from "react"
import { useCoState } from "@/lib/providers/jazz-provider" import { useCoState } from "@/lib/providers/jazz-provider"
import { PublicGlobalGroup } from "@/lib/schema/master/public-group" import { PublicGlobalGroup } from "@/lib/schema/master/public-group"
import { ID } from "jazz-tools" import { Account, ID } from "jazz-tools"
import { Link } from "@/lib/schema" import { Link, Topic } from "@/lib/schema"
const GLOBAL_GROUP_ID = process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID<PublicGlobalGroup> const GLOBAL_GROUP_ID = process.env.NEXT_PUBLIC_JAZZ_GLOBAL_GROUP as ID<PublicGlobalGroup>
export function useTopicData(topicName: string) { export function useTopicData(topicName: string, me: Account | undefined) {
const group = useCoState(PublicGlobalGroup, GLOBAL_GROUP_ID, { const topicID = useMemo(() => me && Topic.findUnique({topicName}, GLOBAL_GROUP_ID, me), [topicName, me])
root: { topics: [] }
})
// const topic = useCoState(Topic, "co_zS3TH4Lkj5MK9GEehinxhjjNTxB" as ID<Topic>, {}) const topic = useCoState(Topic, topicID, {latestGlobalGuide: {sections: [{links: []}]}})
const topic = useMemo(
() => group?.root.topics.find(topic => topic?.name === topicName),
[group?.root.topics, topicName]
)
const allLinks = useMemo(() => { return { topic }
if (!topic?.latestGlobalGuide?.sections) return []
return topic.latestGlobalGuide.sections.flatMap(
section => section?.links?.filter((link): link is Link => !!link?.url) ?? []
)
}, [topic?.latestGlobalGuide?.sections])
return { topic, allLinks }
} }

View File

@@ -39,6 +39,8 @@ export class LaAccount extends Account {
// so just do default profile create provided by jazz-tools // so just do default profile create provided by jazz-tools
super.migrate(creationProps) super.migrate(creationProps)
console.log("In migration", this._refs.root, creationProps)
if (!this._refs.root && creationProps) { if (!this._refs.root && creationProps) {
this.root = UserRoot.create( this.root = UserRoot.create(
{ {

View File

@@ -67,9 +67,9 @@
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"framer-motion": "^11.3.31", "framer-motion": "^11.3.31",
"jazz-react": "0.7.35-new-auth.1", "jazz-react": "0.7.35-unique.2",
"jazz-react-auth-clerk": "0.7.33-new-auth.1", "jazz-react-auth-clerk": "0.7.33-new-auth.1",
"jazz-tools": "0.7.35-new-auth.0", "jazz-tools": "0.7.35-unique.2",
"jotai": "^2.9.3", "jotai": "^2.9.3",
"lowlight": "^3.1.0", "lowlight": "^3.1.0",
"lucide-react": "^0.429.0", "lucide-react": "^0.429.0",