ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
17
fork

Configure Feed

Select the types of activity you want to include in your feed.

replace localStorage context with zustand persist store

Optimization #10:
- created useSettingsStore with zustand persist middleware
- removed SettingsContext.tsx (88 lines) and provider wrapper
- added SSR-safe storage with cross-tab synchronization
- automatic JSON serialization, no manual parse/stringify
- maintained backward-compatible API (useSettings hook)
- bundle size: +2.3KB for zustand library

byarielm.fyi 6b5cf20f 43710263

verified
+104 -96
+33 -3
package-lock.json
··· 26 26 "lucide-react": "^0.544.0", 27 27 "react": "^18.3.1", 28 28 "react-dom": "^18.3.1", 29 - "zod": "^4.2.1" 29 + "zod": "^4.2.1", 30 + "zustand": "^5.0.9" 30 31 }, 31 32 "devDependencies": { 32 33 "@types/jszip": "^3.4.0", ··· 2859 2860 "version": "19.1.14", 2860 2861 "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz", 2861 2862 "integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==", 2862 - "dev": true, 2863 + "devOptional": true, 2863 2864 "license": "MIT", 2864 2865 "dependencies": { 2865 2866 "csstype": "^3.0.2" ··· 4242 4243 "version": "3.1.3", 4243 4244 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 4244 4245 "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 4245 - "dev": true, 4246 + "devOptional": true, 4246 4247 "license": "MIT" 4247 4248 }, 4248 4249 "node_modules/date-fns": { ··· 8279 8280 "license": "MIT", 8280 8281 "funding": { 8281 8282 "url": "https://github.com/sponsors/colinhacks" 8283 + } 8284 + }, 8285 + "node_modules/zustand": { 8286 + "version": "5.0.9", 8287 + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz", 8288 + "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==", 8289 + "license": "MIT", 8290 + "engines": { 8291 + "node": ">=12.20.0" 8292 + }, 8293 + "peerDependencies": { 8294 + "@types/react": ">=18.0.0", 8295 + "immer": ">=9.0.6", 8296 + "react": ">=18.0.0", 8297 + "use-sync-external-store": ">=1.2.0" 8298 + }, 8299 + "peerDependenciesMeta": { 8300 + "@types/react": { 8301 + "optional": true 8302 + }, 8303 + "immer": { 8304 + "optional": true 8305 + }, 8306 + "react": { 8307 + "optional": true 8308 + }, 8309 + "use-sync-external-store": { 8310 + "optional": true 8311 + } 8282 8312 } 8283 8313 } 8284 8314 }
+2 -1
package.json
··· 31 31 "lucide-react": "^0.544.0", 32 32 "react": "^18.3.1", 33 33 "react-dom": "^18.3.1", 34 - "zod": "^4.2.1" 34 + "zod": "^4.2.1", 35 + "zustand": "^5.0.9" 35 36 }, 36 37 "devDependencies": { 37 38 "@types/jszip": "^3.4.0",
+1 -1
src/App.tsx
··· 14 14 import type { UserSettings, SearchResult } from "./types"; 15 15 import { apiClient } from "./lib/api/client"; 16 16 import { ATPROTO_APPS } from "./config/atprotoApps"; 17 - import { useSettings } from "./contexts/SettingsContext"; 17 + import { useSettings } from "./stores/useSettingsStore"; 18 18 19 19 // Lazy load page components 20 20 const LoginPage = lazy(() => import("./pages/Login"));
-87
src/contexts/SettingsContext.tsx
··· 1 - import React, { 2 - createContext, 3 - useContext, 4 - useState, 5 - useEffect, 6 - useCallback, 7 - ReactNode, 8 - } from "react"; 9 - import { DEFAULT_SETTINGS, UserSettings } from "../types/settings"; 10 - 11 - interface SettingsContextType { 12 - settings: UserSettings; 13 - updateSettings: (newSettings: Partial<UserSettings>) => void; 14 - resetSettings: () => void; 15 - isLoading: boolean; 16 - } 17 - 18 - const SettingsContext = createContext<SettingsContextType | undefined>( 19 - undefined, 20 - ); 21 - 22 - export const useSettings = (): SettingsContextType => { 23 - const context = useContext(SettingsContext); 24 - if (!context) { 25 - throw new Error("useSettings must be used within a SettingsProvider"); 26 - } 27 - return context; 28 - }; 29 - 30 - interface SettingsProviderProps { 31 - children: ReactNode; 32 - } 33 - 34 - export const SettingsProvider: React.FC<SettingsProviderProps> = ({ 35 - children, 36 - }) => { 37 - const [settings, setSettings] = useState<UserSettings>(DEFAULT_SETTINGS); 38 - const [isLoading, setIsLoading] = useState(true); 39 - 40 - // Load settings from localStorage on mount 41 - useEffect(() => { 42 - try { 43 - const saved = localStorage.getItem("atlast_settings"); 44 - if (saved) { 45 - const parsed = JSON.parse(saved); 46 - setSettings(parsed); 47 - } 48 - } catch (error) { 49 - console.error("Failed to load settings:", error); 50 - } finally { 51 - setIsLoading(false); 52 - } 53 - }, []); 54 - 55 - // Save settings to localStorage whenever they change 56 - useEffect(() => { 57 - if (!isLoading) { 58 - try { 59 - localStorage.setItem("atlast_settings", JSON.stringify(settings)); 60 - } catch (error) { 61 - console.error("Failed to save settings:", error); 62 - } 63 - } 64 - }, [settings, isLoading]); 65 - 66 - const updateSettings = useCallback((newSettings: Partial<UserSettings>) => { 67 - setSettings((prev) => ({ ...prev, ...newSettings })); 68 - }, []); 69 - 70 - const resetSettings = useCallback(() => { 71 - setSettings(DEFAULT_SETTINGS); 72 - localStorage.removeItem("atlast_settings"); 73 - }, []); 74 - 75 - const value: SettingsContextType = { 76 - settings, 77 - updateSettings, 78 - resetSettings, 79 - isLoading, 80 - }; 81 - 82 - return ( 83 - <SettingsContext.Provider value={value}> 84 - {children} 85 - </SettingsContext.Provider> 86 - ); 87 - };
+1 -4
src/main.tsx
··· 1 1 import React from "react"; 2 2 import ReactDOM from "react-dom/client"; 3 3 import App from "./App"; 4 - import { SettingsProvider } from "./contexts/SettingsContext"; 5 4 import "./index.css"; 6 5 7 6 ReactDOM.createRoot(document.getElementById("root")!).render( 8 7 <React.StrictMode> 9 - <SettingsProvider> 10 - <App /> 11 - </SettingsProvider> 8 + <App /> 12 9 </React.StrictMode>, 13 10 );
+67
src/stores/useSettingsStore.ts
··· 1 + import { create } from "zustand"; 2 + import { persist, createJSONStorage } from "zustand/middleware"; 3 + import { DEFAULT_SETTINGS, UserSettings } from "../types/settings"; 4 + 5 + interface SettingsStore { 6 + settings: UserSettings; 7 + isLoading: boolean; 8 + updateSettings: (newSettings: Partial<UserSettings>) => void; 9 + resetSettings: () => void; 10 + setIsLoading: (loading: boolean) => void; 11 + } 12 + 13 + export const useSettingsStore = create<SettingsStore>()( 14 + persist( 15 + (set) => ({ 16 + settings: DEFAULT_SETTINGS, 17 + isLoading: true, 18 + 19 + updateSettings: (newSettings) => 20 + set((state) => ({ 21 + settings: { ...state.settings, ...newSettings }, 22 + })), 23 + 24 + resetSettings: () => 25 + set({ 26 + settings: DEFAULT_SETTINGS, 27 + }), 28 + 29 + setIsLoading: (loading) => set({ isLoading: loading }), 30 + }), 31 + { 32 + name: "atlast-settings", 33 + storage: createJSONStorage(() => { 34 + // SSR-safe storage 35 + if (typeof window === "undefined") { 36 + return { 37 + getItem: () => null, 38 + setItem: () => {}, 39 + removeItem: () => {}, 40 + }; 41 + } 42 + return window.localStorage; 43 + }), 44 + // Called after rehydration from storage 45 + onRehydrateStorage: () => (state) => { 46 + if (state) { 47 + state.setIsLoading(false); 48 + } 49 + }, 50 + }, 51 + ), 52 + ); 53 + 54 + // Backwards-compatible hook that matches the old context API 55 + export const useSettings = () => { 56 + const settings = useSettingsStore((state) => state.settings); 57 + const updateSettings = useSettingsStore((state) => state.updateSettings); 58 + const resetSettings = useSettingsStore((state) => state.resetSettings); 59 + const isLoading = useSettingsStore((state) => state.isLoading); 60 + 61 + return { 62 + settings, 63 + updateSettings, 64 + resetSettings, 65 + isLoading, 66 + }; 67 + };