an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

notif tabs scroll restoration

rimar1337 d14e2ae8 61ce2144

Changed files
+68 -10
src
+59 -10
src/routes/notifications.tsx
··· 4 4 import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; 5 5 import { useAtom } from "jotai"; 6 6 import * as React from "react"; 7 + import { useEffect, useLayoutEffect } from "react"; 7 8 8 9 import defaultpfp from "~/../public/favicon.png"; 9 10 import { Header } from "~/components/Header"; ··· 14 15 UniversalPostRendererATURILoader, 15 16 } from "~/components/UniversalPostRenderer"; 16 17 import { useAuth } from "~/providers/UnifiedAuthProvider"; 17 - import { constellationURLAtom, imgCDNAtom, isAtTopAtom } from "~/utils/atoms"; 18 + import { 19 + constellationURLAtom, 20 + imgCDNAtom, 21 + isAtTopAtom, 22 + notificationsScrollAtom, 23 + } from "~/utils/atoms"; 18 24 import { 19 25 useInfiniteQueryAuthorFeed, 20 26 useQueryConstellation, ··· 49 55 }); 50 56 51 57 export default function NotificationsTabs() { 52 - const [activeTab, setActiveTab] = React.useState("mentions"); 58 + const [notifState, setNotifState] = useAtom(notificationsScrollAtom); 59 + const activeTab = notifState.activeTab; 53 60 const [isAtTop] = useAtom(isAtTopAtom); 54 - 55 - const scrollPositions = React.useRef<Record<string, number>>({}); 56 61 57 62 const handleValueChange = (newTab: string) => { 58 - scrollPositions.current[activeTab] = window.scrollY; 59 - setActiveTab(newTab); 63 + console.log(newTab); 64 + setNotifState((prev) => { 65 + const wow = { 66 + ...prev, 67 + scrollPositions: { 68 + ...prev.scrollPositions, 69 + [prev.activeTab]: window.scrollY, 70 + }, 71 + activeTab: newTab, 72 + }; 73 + //console.log(wow); 74 + return wow; 75 + }); 60 76 }; 61 77 62 - React.useEffect(() => { 63 - const savedY = scrollPositions.current[activeTab] ?? 0; 64 - window.scrollTo(0, savedY); 65 - }, [activeTab]); 78 + useLayoutEffect(() => { 79 + return () => { 80 + setNotifState((prev) => { 81 + const wow = { 82 + ...prev, 83 + scrollPositions: { 84 + ...prev.scrollPositions, 85 + [activeTab]: window.scrollY, 86 + }, 87 + }; 88 + //console.log(wow); 89 + return wow; 90 + }); 91 + } 92 + // eslint-disable-next-line react-hooks/exhaustive-deps 93 + }, []); 66 94 67 95 return ( 68 96 <TabsPrimitive.Root ··· 138 166 ); 139 167 }, [infiniteMentionsData]); 140 168 169 + const [notifState] = useAtom(notificationsScrollAtom); 170 + const activeTab = notifState.activeTab; 171 + useEffect(() => { 172 + const savedY = notifState.scrollPositions[activeTab] ?? 0; 173 + window.scrollTo(0, savedY); 174 + }, [activeTab, notifState.scrollPositions]); 175 + 141 176 if (isLoading) return <LoadingState text="Loading mentions..." />; 142 177 if (isError) return <ErrorState error={error} />; 143 178 ··· 200 235 ); 201 236 }, [infiniteFollowsData]); 202 237 238 + const [notifState] = useAtom(notificationsScrollAtom); 239 + const activeTab = notifState.activeTab; 240 + useEffect(() => { 241 + const savedY = notifState.scrollPositions[activeTab] ?? 0; 242 + window.scrollTo(0, savedY); 243 + }, [activeTab, notifState.scrollPositions]); 244 + 203 245 if (isLoading) return <LoadingState text="Loading mentions..." />; 204 246 if (isError) return <ErrorState error={error} />; 205 247 ··· 252 294 () => postsData?.pages.flatMap((page) => page.records) ?? [], 253 295 [postsData] 254 296 ); 297 + 298 + const [notifState] = useAtom(notificationsScrollAtom); 299 + const activeTab = notifState.activeTab; 300 + useEffect(() => { 301 + const savedY = notifState.scrollPositions[activeTab] ?? 0; 302 + window.scrollTo(0, savedY); 303 + }, [activeTab, notifState.scrollPositions]); 255 304 256 305 return ( 257 306 <>
+9
src/utils/atoms.ts
··· 21 21 {} 22 22 ); 23 23 24 + type NotificationsScrollState = { 25 + activeTab: string; 26 + scrollPositions: Record<string, number>; 27 + }; 28 + export const notificationsScrollAtom = atom<NotificationsScrollState>({ 29 + activeTab: "mentions", 30 + scrollPositions: {}, 31 + }); 32 + 24 33 export const likedPostsAtom = atomWithStorage<Record<string, string>>( 25 34 "likedPosts", 26 35 {}