persistent_state.md (2227B)
1 # Persistent State 2 3 ``` 4 +-------------------+ 5 setState() --> | PersistentStore | --> subscribers notified 6 +-------------------+ 7 | ^ 8 save| |load on init 9 v | 10 +-------------------+ 11 | localStorage | 12 +-------------------+ 13 ``` 14 15 State that survives page reloads by syncing to localStorage (or sessionStorage). 16 Includes a _version field so you can safely migrate stale data from older 17 schemas. 18 19 ```ts 20 interface Versioned { 21 _version: number; 22 } 23 24 class PersistentStore<T extends Versioned> { 25 private state: T; 26 private subscribers = new Set<(s: T) => void>(); 27 private key: string; 28 private currentVersion: number; 29 private migrate: (old: any) => T; 30 31 constructor(key: string, initial: T, migrate: (old: any) => T) { 32 this.key = key; 33 this.currentVersion = initial._version; 34 this.migrate = migrate; 35 this.state = this.load(initial); 36 } 37 38 private load(initial: T): T { 39 const raw = localStorage.getItem(this.key); 40 if (!raw) return initial; 41 const parsed = JSON.parse(raw); 42 if (parsed._version !== this.currentVersion) return this.migrate(parsed); 43 return parsed as T; 44 } 45 46 private save() { 47 localStorage.setItem(this.key, JSON.stringify(this.state)); 48 } 49 50 getState(): T { 51 return { ...this.state }; 52 } 53 54 setState(updates: Partial<T>) { 55 this.state = { ...this.state, ...updates }; 56 this.save(); 57 this.subscribers.forEach((cb) => cb(this.getState())); 58 } 59 60 subscribe(cb: (s: T) => void) { 61 this.subscribers.add(cb); 62 return () => this.subscribers.delete(cb); 63 } 64 } 65 66 // Usage 67 interface ThemeState extends Versioned { 68 theme: "light" | "dark"; 69 fontSize: number; 70 } 71 72 const themeStore = new PersistentStore<ThemeState>( 73 "theme", 74 { _version: 2, theme: "light", fontSize: 16 }, 75 (old) => ({ _version: 2, theme: old.theme ?? "light", fontSize: 16 }), // migrate v1 → v2 76 ); 77 78 themeStore.setState({ theme: "dark" }); // persisted to localStorage immediately 79 ``` 80 81 Use case: User preferences, session data, or any state that should survive a 82 page refresh — theme, language, last-visited route, partially filled forms.