about summary refs log tree commit diff
path: root/src/state/cache/profile-shadow.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/cache/profile-shadow.ts')
-rw-r--r--src/state/cache/profile-shadow.ts70
1 files changed, 38 insertions, 32 deletions
diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts
index 5323effaf..6ebd39132 100644
--- a/src/state/cache/profile-shadow.ts
+++ b/src/state/cache/profile-shadow.ts
@@ -1,7 +1,8 @@
-import {useEffect, useState, useMemo, useCallback, useRef} from 'react'
+import {useEffect, useState, useMemo, useCallback} from 'react'
 import EventEmitter from 'eventemitter3'
 import {AppBskyActorDefs} from '@atproto/api'
-import {Shadow} from './types'
+import {batchedUpdates} from '#/lib/batchedUpdates'
+import {Shadow, castAsShadow} from './types'
 export type {Shadow} from './types'
 
 const emitter = new EventEmitter()
@@ -22,15 +23,34 @@ type ProfileView =
   | AppBskyActorDefs.ProfileViewBasic
   | AppBskyActorDefs.ProfileViewDetailed
 
-export function useProfileShadow(
-  profile: ProfileView,
-  ifAfterTS: number,
-): Shadow<ProfileView> {
-  const [state, setState] = useState<CacheEntry>({
-    ts: Date.now(),
+const firstSeenMap = new WeakMap<ProfileView, number>()
+function getFirstSeenTS(profile: ProfileView): number {
+  let timeStamp = firstSeenMap.get(profile)
+  if (timeStamp !== undefined) {
+    return timeStamp
+  }
+  timeStamp = Date.now()
+  firstSeenMap.set(profile, timeStamp)
+  return timeStamp
+}
+
+export function useProfileShadow(profile: ProfileView): Shadow<ProfileView> {
+  const profileSeenTS = getFirstSeenTS(profile)
+  const [state, setState] = useState<CacheEntry>(() => ({
+    ts: profileSeenTS,
     value: fromProfile(profile),
-  })
-  const firstRun = useRef(true)
+  }))
+
+  const [prevProfile, setPrevProfile] = useState(profile)
+  if (profile !== prevProfile) {
+    // if we got a new prop, assume it's fresher
+    // than whatever shadow state we accumulated
+    setPrevProfile(profile)
+    setState({
+      ts: profileSeenTS,
+      value: fromProfile(profile),
+    })
+  }
 
   const onUpdate = useCallback(
     (value: Partial<ProfileShadow>) => {
@@ -47,33 +67,20 @@ export function useProfileShadow(
     }
   }, [profile.did, onUpdate])
 
-  // react to profile updates
-  useEffect(() => {
-    // dont fire on first run to avoid needless re-renders
-    if (!firstRun.current) {
-      setState({ts: Date.now(), value: fromProfile(profile)})
-    }
-    firstRun.current = false
-  }, [profile])
-
   return useMemo(() => {
-    return state.ts > ifAfterTS
+    return state.ts > profileSeenTS
       ? mergeShadow(profile, state.value)
-      : {...profile, isShadowed: true}
-  }, [profile, state, ifAfterTS])
+      : castAsShadow(profile)
+  }, [profile, state, profileSeenTS])
 }
 
 export function updateProfileShadow(
   uri: string,
   value: Partial<ProfileShadow>,
 ) {
-  emitter.emit(uri, value)
-}
-
-export function isProfileShadowed<T extends ProfileView>(
-  v: T | Shadow<T>,
-): v is Shadow<T> {
-  return 'isShadowed' in v && !!v.isShadowed
+  batchedUpdates(() => {
+    emitter.emit(uri, value)
+  })
 }
 
 function fromProfile(profile: ProfileView): ProfileShadow {
@@ -88,7 +95,7 @@ function mergeShadow(
   profile: ProfileView,
   shadow: ProfileShadow,
 ): Shadow<ProfileView> {
-  return {
+  return castAsShadow({
     ...profile,
     viewer: {
       ...(profile.viewer || {}),
@@ -96,6 +103,5 @@ function mergeShadow(
       muted: shadow.muted,
       blocking: shadow.blockingUri,
     },
-    isShadowed: true,
-  }
+  })
 }