remove list jitter by initially preferring ssr
This commit is contained in:
parent
eeaf6e10e5
commit
90f4d41fc8
@ -8,19 +8,21 @@ import { CommentFlat } from './comment'
|
|||||||
import { SUB_ITEMS } from '../fragments/subs'
|
import { SUB_ITEMS } from '../fragments/subs'
|
||||||
import { LIMIT } from '../lib/cursor'
|
import { LIMIT } from '../lib/cursor'
|
||||||
import ItemFull from './item-full'
|
import ItemFull from './item-full'
|
||||||
|
import { useData } from './use-data'
|
||||||
|
|
||||||
export default function Items ({ ssrData, variables = {}, query, destructureData, rank, noMoreText, Footer, filter = () => true }) {
|
export default function Items ({ ssrData, variables = {}, query, destructureData, rank, noMoreText, Footer, filter = () => true }) {
|
||||||
const { data, fetchMore } = useQuery(query || SUB_ITEMS, { variables })
|
const { data, fetchMore } = useQuery(query || SUB_ITEMS, { variables })
|
||||||
const Foooter = Footer || MoreFooter
|
const Foooter = Footer || MoreFooter
|
||||||
|
const dat = useData(data, ssrData)
|
||||||
|
|
||||||
const { items, pins, cursor } = useMemo(() => {
|
const { items, pins, cursor } = useMemo(() => {
|
||||||
if (!data && !ssrData) return {}
|
if (!dat) return {}
|
||||||
if (destructureData) {
|
if (destructureData) {
|
||||||
return destructureData(data || ssrData)
|
return destructureData(dat)
|
||||||
} else {
|
} else {
|
||||||
return data?.items || ssrData?.items
|
return dat?.items
|
||||||
}
|
}
|
||||||
}, [data, ssrData])
|
}, [dat])
|
||||||
|
|
||||||
const pinMap = useMemo(() =>
|
const pinMap = useMemo(() =>
|
||||||
pins?.reduce((a, p) => { a[p.position] = p; return a }, {}), [pins])
|
pins?.reduce((a, p) => { a[p.position] = p; return a }, {}), [pins])
|
||||||
@ -28,7 +30,7 @@ export default function Items ({ ssrData, variables = {}, query, destructureData
|
|||||||
const Skeleton = useCallback(() =>
|
const Skeleton = useCallback(() =>
|
||||||
<ItemsSkeleton rank={rank} startRank={items?.length} limit={variables.limit} />, [rank, items])
|
<ItemsSkeleton rank={rank} startRank={items?.length} limit={variables.limit} />, [rank, items])
|
||||||
|
|
||||||
if (!ssrData && !data) {
|
if (!dat) {
|
||||||
return <Skeleton />
|
return <Skeleton />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import styles from './notifications.module.css'
|
|||||||
import { useServiceWorker } from './serviceworker'
|
import { useServiceWorker } from './serviceworker'
|
||||||
import { Checkbox, Form } from './form'
|
import { Checkbox, Form } from './form'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import { useData } from './use-data'
|
||||||
|
|
||||||
function Notification ({ n, fresh }) {
|
function Notification ({ n, fresh }) {
|
||||||
const type = n.__typename
|
const type = n.__typename
|
||||||
@ -340,10 +341,11 @@ const nid = n => n.__typename + n.id + n.sortTime
|
|||||||
export default function Notifications ({ ssrData }) {
|
export default function Notifications ({ ssrData }) {
|
||||||
const { data, fetchMore } = useQuery(NOTIFICATIONS)
|
const { data, fetchMore } = useQuery(NOTIFICATIONS)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const dat = useData(data, ssrData)
|
||||||
|
|
||||||
const { notifications: { notifications, lastChecked, cursor } } = useMemo(() => {
|
const { notifications: { notifications, lastChecked, cursor } } = useMemo(() => {
|
||||||
return data || ssrData || { notifications: {} }
|
return dat || { notifications: {} }
|
||||||
}, [data, ssrData])
|
}, [dat])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastChecked && !router?.query?.checkedAt) {
|
if (lastChecked && !router?.query?.checkedAt) {
|
||||||
@ -358,7 +360,7 @@ export default function Notifications ({ ssrData }) {
|
|||||||
}
|
}
|
||||||
}, [router, lastChecked])
|
}, [router, lastChecked])
|
||||||
|
|
||||||
if (!data && !ssrData) return <CommentsFlatSkeleton />
|
if (!dat) return <CommentsFlatSkeleton />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.fresh {
|
.fresh {
|
||||||
background-color: rgba(0, 0, 0, 0.03);
|
background-color: rgba(128, 128, 128, 0.1);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
components/use-data.js
Normal file
27
components/use-data.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
/*
|
||||||
|
What we want is to use ssrData if it exists, until cache data changes
|
||||||
|
... this prevents item list jitter where the intially rendered items
|
||||||
|
are stale until the cache is rewritten with incoming ssrData
|
||||||
|
*/
|
||||||
|
export function useData (data, ssrData) {
|
||||||
|
// when fresh is true, it means data has been updated after the initial render and it's populated
|
||||||
|
const [fresh, setFresh] = useState(false)
|
||||||
|
|
||||||
|
// on first render, we want to use ssrData if it's available
|
||||||
|
// it's only unavailable on back/forward navigation
|
||||||
|
const ref = useRef(true)
|
||||||
|
const firstRender = ref.current
|
||||||
|
ref.current = false
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!firstRender && !fresh && data) setFresh(true)
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
// if we don't have data yet, use ssrData
|
||||||
|
// if we have data, but it's not fresh, use ssrData
|
||||||
|
// unless we don't have ssrData
|
||||||
|
if (!data || (!fresh && ssrData)) return ssrData
|
||||||
|
return data
|
||||||
|
}
|
@ -7,6 +7,7 @@ import userStyles from './user-header.module.css'
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import MoreFooter from './more-footer'
|
import MoreFooter from './more-footer'
|
||||||
|
import { useData } from './use-data'
|
||||||
|
|
||||||
// all of this nonsense is to show the stat we are sorting by first
|
// all of this nonsense is to show the stat we are sorting by first
|
||||||
const Stacked = ({ user }) => (<span>{abbrNum(user.stacked)} stacked</span>)
|
const Stacked = ({ user }) => (<span>{abbrNum(user.stacked)} stacked</span>)
|
||||||
@ -37,6 +38,7 @@ function seperate (arr, seperator) {
|
|||||||
|
|
||||||
export default function UserList ({ ssrData, query, variables, destructureData }) {
|
export default function UserList ({ ssrData, query, variables, destructureData }) {
|
||||||
const { data, fetchMore } = useQuery(query, { variables })
|
const { data, fetchMore } = useQuery(query, { variables })
|
||||||
|
const dat = useData(data, ssrData)
|
||||||
const [statComps, setStatComps] = useState(seperate(STAT_COMPONENTS, Seperator))
|
const [statComps, setStatComps] = useState(seperate(STAT_COMPONENTS, Seperator))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -48,15 +50,15 @@ export default function UserList ({ ssrData, query, variables, destructureData }
|
|||||||
}, [variables?.by])
|
}, [variables?.by])
|
||||||
|
|
||||||
const { users, cursor } = useMemo(() => {
|
const { users, cursor } = useMemo(() => {
|
||||||
if (!data && !ssrData) return {}
|
if (!dat) return {}
|
||||||
if (destructureData) {
|
if (destructureData) {
|
||||||
return destructureData(data || ssrData)
|
return destructureData(dat)
|
||||||
} else {
|
} else {
|
||||||
return data || ssrData
|
return dat
|
||||||
}
|
}
|
||||||
}, [data, ssrData])
|
}, [dat])
|
||||||
|
|
||||||
if (!ssrData && !data) {
|
if (!dat) {
|
||||||
return <UsersSkeleton />
|
return <UsersSkeleton />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user