diff --git a/components/items.js b/components/items.js index 7a7a38b2..547b2986 100644 --- a/components/items.js +++ b/components/items.js @@ -8,19 +8,21 @@ import { CommentFlat } from './comment' import { SUB_ITEMS } from '../fragments/subs' import { LIMIT } from '../lib/cursor' import ItemFull from './item-full' +import { useData } from './use-data' export default function Items ({ ssrData, variables = {}, query, destructureData, rank, noMoreText, Footer, filter = () => true }) { const { data, fetchMore } = useQuery(query || SUB_ITEMS, { variables }) const Foooter = Footer || MoreFooter + const dat = useData(data, ssrData) const { items, pins, cursor } = useMemo(() => { - if (!data && !ssrData) return {} + if (!dat) return {} if (destructureData) { - return destructureData(data || ssrData) + return destructureData(dat) } else { - return data?.items || ssrData?.items + return dat?.items } - }, [data, ssrData]) + }, [dat]) const pinMap = useMemo(() => 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(() => , [rank, items]) - if (!ssrData && !data) { + if (!dat) { return } diff --git a/components/notifications.js b/components/notifications.js index 8f9898cb..37484712 100644 --- a/components/notifications.js +++ b/components/notifications.js @@ -20,6 +20,7 @@ import styles from './notifications.module.css' import { useServiceWorker } from './serviceworker' import { Checkbox, Form } from './form' import { useRouter } from 'next/router' +import { useData } from './use-data' function Notification ({ n, fresh }) { const type = n.__typename @@ -340,10 +341,11 @@ const nid = n => n.__typename + n.id + n.sortTime export default function Notifications ({ ssrData }) { const { data, fetchMore } = useQuery(NOTIFICATIONS) const router = useRouter() + const dat = useData(data, ssrData) const { notifications: { notifications, lastChecked, cursor } } = useMemo(() => { - return data || ssrData || { notifications: {} } - }, [data, ssrData]) + return dat || { notifications: {} } + }, [dat]) useEffect(() => { if (lastChecked && !router?.query?.checkedAt) { @@ -358,7 +360,7 @@ export default function Notifications ({ ssrData }) { } }, [router, lastChecked]) - if (!data && !ssrData) return + if (!dat) return return ( <> diff --git a/components/notifications.module.css b/components/notifications.module.css index 7305f949..600ae419 100644 --- a/components/notifications.module.css +++ b/components/notifications.module.css @@ -1,5 +1,5 @@ .fresh { - background-color: rgba(0, 0, 0, 0.03); + background-color: rgba(128, 128, 128, 0.1); border-radius: 0; } diff --git a/components/use-data.js b/components/use-data.js new file mode 100644 index 00000000..9ac52f6d --- /dev/null +++ b/components/use-data.js @@ -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 +} diff --git a/components/user-list.js b/components/user-list.js index 582da750..23ec9e04 100644 --- a/components/user-list.js +++ b/components/user-list.js @@ -7,6 +7,7 @@ import userStyles from './user-header.module.css' import { useEffect, useMemo, useState } from 'react' import { useQuery } from '@apollo/client' import MoreFooter from './more-footer' +import { useData } from './use-data' // all of this nonsense is to show the stat we are sorting by first const Stacked = ({ user }) => ({abbrNum(user.stacked)} stacked) @@ -37,6 +38,7 @@ function seperate (arr, seperator) { export default function UserList ({ ssrData, query, variables, destructureData }) { const { data, fetchMore } = useQuery(query, { variables }) + const dat = useData(data, ssrData) const [statComps, setStatComps] = useState(seperate(STAT_COMPONENTS, Seperator)) useEffect(() => { @@ -48,15 +50,15 @@ export default function UserList ({ ssrData, query, variables, destructureData } }, [variables?.by]) const { users, cursor } = useMemo(() => { - if (!data && !ssrData) return {} + if (!dat) return {} if (destructureData) { - return destructureData(data || ssrData) + return destructureData(dat) } else { - return data || ssrData + return dat } - }, [data, ssrData]) + }, [dat]) - if (!ssrData && !data) { + if (!dat) { return }