Compare commits

...

10 Commits

Author SHA1 Message Date
Eric Bailey 53f65fac32 Add some logs 2024-04-03 13:54:03 -05:00
Dan Abramov 2e2e554f07 Scope query client per DID 2024-03-22 00:06:25 +00:00
Dan Abramov 8e8bcfbc75 Remove useQueryClient dependency from session provider 2024-03-21 23:41:44 +00:00
Dan Abramov 9e36c4abb0 Pull useQueryClient-dependent code down in App.native 2024-03-21 23:38:55 +00:00
Dan Abramov 17b014f4ae Move QueryProvider inside the key 2024-03-21 23:30:33 +00:00
Dan Abramov 57a72a1108 Factor out QueryProvider 2024-03-21 23:24:17 +00:00
Dan Abramov 19031bc699 Make notifications init reactive to queryClient 2024-03-21 23:14:51 +00:00
Dan Abramov f18b801979 Pass queryClient explicitly to updateProfileShadow 2024-03-21 22:53:00 +00:00
Dan Abramov e56b7e00b5 Pass queryClient explicitly to updatePostShadow 2024-03-21 22:47:02 +00:00
Dan Abramov 698ba036dd Pass queryClient explicitly to resetProfilePostsQueries 2024-03-21 22:41:24 +00:00
13 changed files with 461 additions and 380 deletions
+54 -52
View File
@@ -12,19 +12,15 @@ import {
import * as SplashScreen from 'expo-splash-screen'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client'
import {useQueryClient} from '@tanstack/react-query'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {init as initPersistedState} from '#/state/persisted'
import * as persisted from '#/state/persisted'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import * as notifications from 'lib/notifications/notifications'
import {
asyncStoragePersister,
dehydrateOptions,
queryClient,
} from 'lib/react-query'
import {useNotificationsListener} from 'lib/notifications/notifications'
import {QueryProvider} from 'lib/react-query'
import {s} from 'lib/styles'
import {ThemeProvider} from 'lib/ThemeContext'
import {Provider as DialogStateProvider} from 'state/dialogs'
@@ -59,11 +55,11 @@ function InnerApp() {
const {resumeSession} = useSessionApi()
const theme = useColorModeTheme()
const {_} = useLingui()
useIntentHandler()
// init
useEffect(() => {
notifications.init(queryClient)
listenSessionDropped(() => {
Toast.show(_(msg`Sorry! Your session expired. Please log in again.`))
})
@@ -79,25 +75,29 @@ function InnerApp() {
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<StatsigProvider>
<LabelDefsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={theme}>
{/* All components should be within this provider */}
<RootSiblingParent>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</RootSiblingParent>
</ThemeProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</LabelDefsProvider>
</StatsigProvider>
<QueryProvider currentDid={currentAccount?.did}>
<PushNotificationsListener>
<StatsigProvider>
<LabelDefsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={theme}>
{/* All components should be within this provider */}
<RootSiblingParent>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</RootSiblingParent>
</ThemeProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</LabelDefsProvider>
</StatsigProvider>
</PushNotificationsListener>
</QueryProvider>
</React.Fragment>
</Splash>
</Alf>
@@ -105,6 +105,12 @@ function InnerApp() {
)
}
function PushNotificationsListener({children}: {children: React.ReactNode}) {
const queryClient = useQueryClient()
useNotificationsListener(queryClient)
return children
}
function App() {
const [isReady, setReady] = useState(false)
@@ -121,31 +127,27 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{persister: asyncStoragePersister, dehydrateOptions}}>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<MutedThreadsProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<I18nProvider>
<PortalProvider>
<InnerApp />
</PortalProvider>
</I18nProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</MutedThreadsProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
</PersistQueryClientProvider>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<MutedThreadsProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<I18nProvider>
<PortalProvider>
<InnerApp />
</PortalProvider>
</I18nProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</MutedThreadsProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
)
}
+66 -74
View File
@@ -1,44 +1,38 @@
import 'lib/sentry' // must be near top
import React, {useState, useEffect} from 'react'
import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {RootSiblingParent} from 'react-native-root-siblings'
import 'view/icons'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import React, {useEffect, useState} from 'react'
import {RootSiblingParent} from 'react-native-root-siblings'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {init as initPersistedState} from '#/state/persisted'
import {Shell} from 'view/shell/index'
import {ToastContainer} from 'view/com/util/Toast.web'
import {ThemeProvider} from 'lib/ThemeContext'
import {
queryClient,
asyncStoragePersister,
dehydrateOptions,
} from 'lib/react-query'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as DialogStateProvider} from 'state/dialogs'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
import * as persisted from '#/state/persisted'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import I18nProvider from './locale/i18nProvider'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import {QueryProvider} from 'lib/react-query'
import {ThemeProvider} from 'lib/ThemeContext'
import {Provider as DialogStateProvider} from 'state/dialogs'
import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as PrefsStateProvider} from 'state/preferences'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import {
Provider as SessionProvider,
useSession,
useSessionApi,
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out'
import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed'
import {ToastContainer} from 'view/com/util/Toast.web'
import {Shell} from 'view/shell/index'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {Provider as PortalProvider} from '#/components/Portal'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import I18nProvider from './locale/i18nProvider'
function InnerApp() {
const {isInitialLoad, currentAccount} = useSession()
@@ -60,25 +54,27 @@ function InnerApp() {
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<StatsigProvider>
<LabelDefsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={theme}>
{/* All components should be within this provider */}
<RootSiblingParent>
<SafeAreaProvider>
<Shell />
</SafeAreaProvider>
</RootSiblingParent>
<ToastContainer />
</ThemeProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</LabelDefsProvider>
</StatsigProvider>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<LabelDefsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={theme}>
{/* All components should be within this provider */}
<RootSiblingParent>
<SafeAreaProvider>
<Shell />
</SafeAreaProvider>
</RootSiblingParent>
<ToastContainer />
</ThemeProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</LabelDefsProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
</Alf>
)
@@ -100,31 +96,27 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{persister: asyncStoragePersister, dehydrateOptions}}>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<MutedThreadsProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<I18nProvider>
<PortalProvider>
<InnerApp />
</PortalProvider>
</I18nProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</MutedThreadsProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
</PersistQueryClientProvider>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
<MutedThreadsProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<I18nProvider>
<PortalProvider>
<InnerApp />
</PortalProvider>
</I18nProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</MutedThreadsProvider>
</PrefsStateProvider>
</ShellStateProvider>
</SessionProvider>
)
}
+61 -49
View File
@@ -1,12 +1,14 @@
import {useEffect} from 'react'
import * as Notifications from 'expo-notifications'
import {QueryClient} from '@tanstack/react-query'
import {resetToTab} from '../../Navigation'
import {devicePlatform, isIOS} from 'platform/detection'
import {track} from 'lib/analytics/analytics'
import {logger} from '#/logger'
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
import {truncateAndInvalidate} from '#/state/queries/util'
import {SessionAccount, getAgent} from '#/state/session'
import {getAgent, SessionAccount} from '#/state/session'
import {track} from 'lib/analytics/analytics'
import {devicePlatform, isIOS} from 'platform/detection'
import {resetToTab} from '../../Navigation'
import {logEvent} from '../statsig/statsig'
const SERVICE_DID = (serviceUrl?: string) =>
@@ -80,53 +82,63 @@ export function registerTokenChangeHandler(
}
}
export function init(queryClient: QueryClient) {
// handle notifications that are received, both in the foreground or background
// NOTE: currently just here for debug logging
Notifications.addNotificationReceivedListener(event => {
logger.debug(
'Notifications: received',
{event},
logger.DebugContext.notifications,
)
if (event.request.trigger.type === 'push') {
// handle payload-based deeplinks
let payload
if (isIOS) {
payload = event.request.trigger.payload
} else {
// TODO: handle android payload deeplink
}
if (payload) {
logger.debug(
'Notifications: received payload',
payload,
logger.DebugContext.notifications,
)
// TODO: deeplink notif here
}
}
})
// handle notifications that are tapped on
Notifications.addNotificationResponseReceivedListener(response => {
logger.debug(
'Notifications: response received',
{
actionIdentifier: response.actionIdentifier,
},
logger.DebugContext.notifications,
)
if (response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER) {
export function useNotificationsListener(queryClient: QueryClient) {
useEffect(() => {
// handle notifications that are received, both in the foreground or background
// NOTE: currently just here for debug logging
const sub1 = Notifications.addNotificationReceivedListener(event => {
logger.debug(
'User pressed a notification, opening notifications tab',
{},
'Notifications: received',
{event},
logger.DebugContext.notifications,
)
track('Notificatons:OpenApp')
logEvent('notifications:openApp', {})
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
resetToTab('NotificationsTab') // open notifications tab
if (event.request.trigger.type === 'push') {
// handle payload-based deeplinks
let payload
if (isIOS) {
payload = event.request.trigger.payload
} else {
// TODO: handle android payload deeplink
}
if (payload) {
logger.debug(
'Notifications: received payload',
payload,
logger.DebugContext.notifications,
)
// TODO: deeplink notif here
}
}
})
// handle notifications that are tapped on
const sub2 = Notifications.addNotificationResponseReceivedListener(
response => {
logger.debug(
'Notifications: response received',
{
actionIdentifier: response.actionIdentifier,
},
logger.DebugContext.notifications,
)
if (
response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER
) {
logger.debug(
'User pressed a notification, opening notifications tab',
{},
logger.DebugContext.notifications,
)
track('Notificatons:OpenApp')
logEvent('notifications:openApp', {})
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
resetToTab('NotificationsTab') // open notifications tab
}
},
)
return () => {
sub1.remove()
sub2.remove()
}
})
}, [queryClient])
}
-70
View File
@@ -1,70 +0,0 @@
import {AppState, AppStateStatus} from 'react-native'
import {QueryClient, focusManager} from '@tanstack/react-query'
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
import AsyncStorage from '@react-native-async-storage/async-storage'
import {PersistQueryClientProviderProps} from '@tanstack/react-query-persist-client'
import {isNative} from '#/platform/detection'
// any query keys in this array will be persisted to AsyncStorage
const STORED_CACHE_QUERY_KEYS = ['labelers-detailed-info']
focusManager.setEventListener(onFocus => {
if (isNative) {
const subscription = AppState.addEventListener(
'change',
(status: AppStateStatus) => {
focusManager.setFocused(status === 'active')
},
)
return () => subscription.remove()
} else if (typeof window !== 'undefined' && window.addEventListener) {
// these handlers are a bit redundant but focus catches when the browser window
// is blurred/focused while visibilitychange seems to only handle when the
// window minimizes (both of them catch tab changes)
// there's no harm to redundant fires because refetchOnWindowFocus is only
// used with queries that employ stale data times
const handler = () => onFocus()
window.addEventListener('focus', handler, false)
window.addEventListener('visibilitychange', handler, false)
return () => {
window.removeEventListener('visibilitychange', handler)
window.removeEventListener('focus', handler)
}
}
})
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
// NOTE
// refetchOnWindowFocus breaks some UIs (like feeds)
// so we only selectively want to enable this
// -prf
refetchOnWindowFocus: false,
// Structural sharing between responses makes it impossible to rely on
// "first seen" timestamps on objects to determine if they're fresh.
// Disable this optimization so that we can rely on "first seen" timestamps.
structuralSharing: false,
// We don't want to retry queries by default, because in most cases we
// want to fail early and show a response to the user. There are
// exceptions, and those can be made on a per-query basis. For others, we
// should give users controls to retry.
retry: false,
},
},
})
export const asyncStoragePersister = createAsyncStoragePersister({
storage: AsyncStorage,
key: 'queryCache',
})
export const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] =
{
shouldDehydrateMutation: (_: any) => false,
shouldDehydrateQuery: query => {
return STORED_CACHE_QUERY_KEYS.includes(String(query.queryKey[0]))
},
}
+124
View File
@@ -0,0 +1,124 @@
import React, {useRef, useState} from 'react'
import {AppState, AppStateStatus} from 'react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
import {focusManager, QueryClient} from '@tanstack/react-query'
import {
PersistQueryClientProvider,
PersistQueryClientProviderProps,
} from '@tanstack/react-query-persist-client'
import {isNative} from '#/platform/detection'
// any query keys in this array will be persisted to AsyncStorage
const STORED_CACHE_QUERY_KEYS = ['labelers-detailed-info']
focusManager.setEventListener(onFocus => {
if (isNative) {
const subscription = AppState.addEventListener(
'change',
(status: AppStateStatus) => {
focusManager.setFocused(status === 'active')
},
)
return () => subscription.remove()
} else if (typeof window !== 'undefined' && window.addEventListener) {
// these handlers are a bit redundant but focus catches when the browser window
// is blurred/focused while visibilitychange seems to only handle when the
// window minimizes (both of them catch tab changes)
// there's no harm to redundant fires because refetchOnWindowFocus is only
// used with queries that employ stale data times
const handler = () => onFocus()
window.addEventListener('focus', handler, false)
window.addEventListener('visibilitychange', handler, false)
return () => {
window.removeEventListener('visibilitychange', handler)
window.removeEventListener('focus', handler)
}
}
})
const createQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
// NOTE
// refetchOnWindowFocus breaks some UIs (like feeds)
// so we only selectively want to enable this
// -prf
refetchOnWindowFocus: false,
// Structural sharing between responses makes it impossible to rely on
// "first seen" timestamps on objects to determine if they're fresh.
// Disable this optimization so that we can rely on "first seen" timestamps.
structuralSharing: false,
// We don't want to retry queries by default, because in most cases we
// want to fail early and show a response to the user. There are
// exceptions, and those can be made on a per-query basis. For others, we
// should give users controls to retry.
retry: false,
},
},
})
const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] =
{
shouldDehydrateMutation: (_: any) => false,
shouldDehydrateQuery: query => {
return STORED_CACHE_QUERY_KEYS.includes(String(query.queryKey[0]))
},
}
export function QueryProvider({
children,
currentDid,
}: {
children: React.ReactNode
currentDid: string | undefined
}) {
return (
<QueryProviderInner
// Enforce we never reuse cache between users.
// These two props MUST stay in sync.
key={currentDid}
currentDid={currentDid}>
{children}
</QueryProviderInner>
)
}
function QueryProviderInner({
children,
currentDid,
}: {
children: React.ReactNode
currentDid: string | undefined
}) {
const initialDid = useRef(currentDid)
if (currentDid !== initialDid.current) {
throw Error(
'Something is very wrong. Expected did to be stable due to key above.',
)
}
// We create the query client here so that it's scoped to a specific DID.
// Do not move the query client creation outside of this component.
const [queryClient, _setQueryClient] = useState(() => createQueryClient())
const [persistOptions, _setPersistOptions] = useState(() => {
const asyncPersister = createAsyncStoragePersister({
storage: AsyncStorage,
key: 'queryClient-' + (currentDid ?? 'logged-out'),
})
return {
persister: asyncPersister,
dehydrateOptions,
}
})
console.log('QUERY', currentDid)
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={persistOptions}>
{children}
</PersistQueryClientProvider>
)
}
+12 -6
View File
@@ -1,13 +1,14 @@
import {useEffect, useState, useMemo} from 'react'
import EventEmitter from 'eventemitter3'
import {useEffect, useMemo, useState} from 'react'
import {AppBskyFeedDefs} from '@atproto/api'
import {QueryClient} from '@tanstack/react-query'
import EventEmitter from 'eventemitter3'
import {batchedUpdates} from '#/lib/batchedUpdates'
import {Shadow, castAsShadow} from './types'
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '../queries/notifications/feed'
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '../queries/post-feed'
import {findAllPostsInQueryData as findAllPostsInThreadQueryData} from '../queries/post-thread'
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '../queries/search-posts'
import {queryClient} from 'lib/react-query'
import {castAsShadow, Shadow} from './types'
export type {Shadow} from './types'
export interface PostShadow {
@@ -93,8 +94,12 @@ function mergeShadow(
})
}
export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
const cachedPosts = findPostsInCache(uri)
export function updatePostShadow(
queryClient: QueryClient,
uri: string,
value: Partial<PostShadow>,
) {
const cachedPosts = findPostsInCache(queryClient, uri)
for (let post of cachedPosts) {
shadows.set(post, {...shadows.get(post), ...value})
}
@@ -104,6 +109,7 @@ export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
}
function* findPostsInCache(
queryClient: QueryClient,
uri: string,
): Generator<AppBskyFeedDefs.PostView, void> {
for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
+9 -6
View File
@@ -1,7 +1,10 @@
import {useEffect, useState, useMemo} from 'react'
import EventEmitter from 'eventemitter3'
import {useEffect, useMemo, useState} from 'react'
import {AppBskyActorDefs} from '@atproto/api'
import {QueryClient} from '@tanstack/react-query'
import EventEmitter from 'eventemitter3'
import {batchedUpdates} from '#/lib/batchedUpdates'
import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members'
import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts'
import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '../queries/my-muted-accounts'
@@ -11,9 +14,7 @@ import {findAllProfilesInQueryData as findAllProfilesInProfileQueryData} from '.
import {findAllProfilesInQueryData as findAllProfilesInProfileFollowersQueryData} from '../queries/profile-followers'
import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '../queries/profile-follows'
import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '../queries/suggested-follows'
import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
import {Shadow, castAsShadow} from './types'
import {queryClient} from 'lib/react-query'
import {castAsShadow, Shadow} from './types'
export type {Shadow} from './types'
export interface ProfileShadow {
@@ -58,10 +59,11 @@ export function useProfileShadow<
}
export function updateProfileShadow(
queryClient: QueryClient,
did: string,
value: Partial<ProfileShadow>,
) {
const cachedProfiles = findProfilesInCache(did)
const cachedProfiles = findProfilesInCache(queryClient, did)
for (let post of cachedProfiles) {
shadows.set(post, {...shadows.get(post), ...value})
}
@@ -90,6 +92,7 @@ function mergeShadow<TProfileView extends AppBskyActorDefs.ProfileView>(
}
function* findProfilesInCache(
queryClient: QueryClient,
did: string,
): Generator<AppBskyActorDefs.ProfileView, void> {
yield* findAllProfilesInListMembersQueryData(queryClient, did)
+24 -20
View File
@@ -3,37 +3,37 @@ import {AppState} from 'react-native'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
ModerationDecision,
AtUri,
ModerationDecision,
} from '@atproto/api'
import {
useInfiniteQuery,
InfiniteData,
QueryKey,
QueryClient,
QueryKey,
useInfiniteQuery,
useQueryClient,
} from '@tanstack/react-query'
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import {useFeedTuners} from '../preferences/feed-tuners'
import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types'
import {FollowingFeedAPI} from 'lib/api/feed/following'
import {AuthorFeedAPI} from 'lib/api/feed/author'
import {LikesFeedAPI} from 'lib/api/feed/likes'
import {CustomFeedAPI} from 'lib/api/feed/custom'
import {ListFeedAPI} from 'lib/api/feed/list'
import {MergeFeedAPI} from 'lib/api/feed/merge'
import {HomeFeedAPI} from '#/lib/api/feed/home'
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
import {logger} from '#/logger'
import {STALE} from '#/state/queries'
import {precacheFeedPostProfiles} from './profile'
import {getAgent} from '#/state/session'
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
import {useModerationOpts} from './preferences'
import {queryClient} from 'lib/react-query'
import {getAgent} from '#/state/session'
import {AuthorFeedAPI} from 'lib/api/feed/author'
import {CustomFeedAPI} from 'lib/api/feed/custom'
import {FollowingFeedAPI} from 'lib/api/feed/following'
import {LikesFeedAPI} from 'lib/api/feed/likes'
import {ListFeedAPI} from 'lib/api/feed/list'
import {MergeFeedAPI} from 'lib/api/feed/merge'
import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types'
import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
import {BSKY_FEED_OWNER_DIDS} from 'lib/constants'
import {KnownError} from '#/view/com/posts/FeedErrorMessage'
import {useFeedTuners} from '../preferences/feed-tuners'
import {useModerationOpts} from './preferences'
import {precacheFeedPostProfiles} from './profile'
import {embedViewRecordToPostView, getEmbeddedPost} from './util'
type ActorDid = string
type AuthorFilter =
@@ -458,7 +458,11 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
}
}
export function resetProfilePostsQueries(did: string, timeout = 0) {
export function resetProfilePostsQueries(
queryClient: QueryClient,
did: string,
timeout = 0,
) {
setTimeout(() => {
queryClient.resetQueries({
predicate: query =>
+20 -16
View File
@@ -1,12 +1,13 @@
import {useCallback} from 'react'
import {AppBskyFeedDefs, AtUri} from '@atproto/api'
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {track} from '#/lib/analytics/analytics'
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
import {updatePostShadow} from '#/state/cache/post-shadow'
import {Shadow} from '#/state/cache/types'
import {getAgent} from '#/state/session'
import {updatePostShadow} from '#/state/cache/post-shadow'
import {track} from '#/lib/analytics/analytics'
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
export const RQKEY = (postUri: string) => ['post', postUri]
@@ -62,6 +63,7 @@ export function usePostLikeMutationQueue(
logContext: LogEvents['post:like']['logContext'] &
LogEvents['post:unlike']['logContext'],
) {
const queryClient = useQueryClient()
const postUri = post.uri
const postCid = post.cid
const initialLikeUri = post.viewer?.like
@@ -89,7 +91,7 @@ export function usePostLikeMutationQueue(
},
onSuccess(finalLikeUri) {
// finalize
updatePostShadow(postUri, {
updatePostShadow(queryClient, postUri, {
likeUri: finalLikeUri,
})
},
@@ -97,19 +99,19 @@ export function usePostLikeMutationQueue(
const queueLike = useCallback(() => {
// optimistically update
updatePostShadow(postUri, {
updatePostShadow(queryClient, postUri, {
likeUri: 'pending',
})
return queueToggle(true)
}, [postUri, queueToggle])
}, [queryClient, postUri, queueToggle])
const queueUnlike = useCallback(() => {
// optimistically update
updatePostShadow(postUri, {
updatePostShadow(queryClient, postUri, {
likeUri: undefined,
})
return queueToggle(false)
}, [postUri, queueToggle])
}, [queryClient, postUri, queueToggle])
return [queueLike, queueUnlike]
}
@@ -149,6 +151,7 @@ export function usePostRepostMutationQueue(
logContext: LogEvents['post:repost']['logContext'] &
LogEvents['post:unrepost']['logContext'],
) {
const queryClient = useQueryClient()
const postUri = post.uri
const postCid = post.cid
const initialRepostUri = post.viewer?.repost
@@ -176,7 +179,7 @@ export function usePostRepostMutationQueue(
},
onSuccess(finalRepostUri) {
// finalize
updatePostShadow(postUri, {
updatePostShadow(queryClient, postUri, {
repostUri: finalRepostUri,
})
},
@@ -184,19 +187,19 @@ export function usePostRepostMutationQueue(
const queueRepost = useCallback(() => {
// optimistically update
updatePostShadow(postUri, {
updatePostShadow(queryClient, postUri, {
repostUri: 'pending',
})
return queueToggle(true)
}, [postUri, queueToggle])
}, [queryClient, postUri, queueToggle])
const queueUnrepost = useCallback(() => {
// optimistically update
updatePostShadow(postUri, {
updatePostShadow(queryClient, postUri, {
repostUri: undefined,
})
return queueToggle(false)
}, [postUri, queueToggle])
}, [queryClient, postUri, queueToggle])
return [queueRepost, queueUnrepost]
}
@@ -234,12 +237,13 @@ function usePostUnrepostMutation(
}
export function usePostDeleteMutation() {
const queryClient = useQueryClient()
return useMutation<void, Error, {uri: string}>({
mutationFn: async ({uri}) => {
await getAgent().deletePost(uri)
},
onSuccess(data, variables) {
updatePostShadow(variables.uri, {isDeleted: true})
updatePostShadow(queryClient, variables.uri, {isDeleted: true})
track('Post:Delete')
},
})
+17 -15
View File
@@ -1,33 +1,33 @@
import {useMemo, createContext, useContext} from 'react'
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
import {createContext, useContext, useMemo} from 'react'
import {
LabelPreference,
BskyFeedViewPreference,
ModerationOpts,
AppBskyActorDefs,
BSKY_LABELER_DID,
BskyFeedViewPreference,
LabelPreference,
ModerationOpts,
} from '@atproto/api'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {track} from '#/lib/analytics/analytics'
import {getAge} from '#/lib/strings/time'
import {getAgent, useSession} from '#/state/session'
import {
UsePreferencesQueryResponse,
ThreadViewPreferences,
} from '#/state/queries/preferences/types'
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
import {STALE} from '#/state/queries'
import {
DEFAULT_HOME_FEED_PREFS,
DEFAULT_THREAD_VIEW_PREFS,
DEFAULT_LOGGED_OUT_PREFERENCES,
DEFAULT_THREAD_VIEW_PREFS,
} from '#/state/queries/preferences/const'
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
import {STALE} from '#/state/queries'
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
import {
ThreadViewPreferences,
UsePreferencesQueryResponse,
} from '#/state/queries/preferences/types'
import {getAgent, useSession} from '#/state/session'
import {saveLabelers} from '#/state/session/agent-config'
export * from '#/state/queries/preferences/types'
export * from '#/state/queries/preferences/moderation'
export * from '#/state/queries/preferences/const'
export * from '#/state/queries/preferences/moderation'
export * from '#/state/queries/preferences/types'
export const preferencesQueryKey = ['getPreferences']
@@ -40,6 +40,8 @@ export function usePreferencesQuery() {
queryFn: async () => {
const agent = getAgent()
console.log('GET PREFERENCES', agent.session?.did)
if (agent.session?.did === undefined) {
return DEFAULT_LOGGED_OUT_PREFERENCES
} else {
+36 -31
View File
@@ -1,32 +1,33 @@
import {useCallback} from 'react'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {
AtUri,
AppBskyActorDefs,
AppBskyActorProfile,
AppBskyActorGetProfile,
AppBskyFeedDefs,
AppBskyActorProfile,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AtUri,
} from '@atproto/api'
import {
QueryClient,
useMutation,
useQuery,
useQueryClient,
useMutation,
QueryClient,
} from '@tanstack/react-query'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {useSession, getAgent} from '../session'
import {updateProfileShadow} from '../cache/profile-shadow'
import {track} from '#/lib/analytics/analytics'
import {uploadBlob} from '#/lib/api'
import {until} from '#/lib/async/until'
import {Shadow} from '#/state/cache/types'
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
import {STALE} from '#/state/queries'
import {track} from '#/lib/analytics/analytics'
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
import {Shadow} from '#/state/cache/types'
import {STALE} from '#/state/queries'
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
import {updateProfileShadow} from '../cache/profile-shadow'
import {getAgent, useSession} from '../session'
import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts'
import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts'
import {ThreadNode} from './post-thread'
export const RQKEY = (did: string) => ['profile', did]
@@ -190,6 +191,7 @@ export function useProfileFollowMutationQueue(
logContext: LogEvents['profile:follow']['logContext'] &
LogEvents['profile:unfollow']['logContext'],
) {
const queryClient = useQueryClient()
const did = profile.did
const initialFollowingUri = profile.viewer?.following
const followMutation = useProfileFollowMutation(logContext)
@@ -215,7 +217,7 @@ export function useProfileFollowMutationQueue(
},
onSuccess(finalFollowingUri) {
// finalize
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
followingUri: finalFollowingUri,
})
},
@@ -223,19 +225,19 @@ export function useProfileFollowMutationQueue(
const queueFollow = useCallback(() => {
// optimistically update
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
followingUri: 'pending',
})
return queueToggle(true)
}, [did, queueToggle])
}, [queryClient, did, queueToggle])
const queueUnfollow = useCallback(() => {
// optimistically update
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
followingUri: undefined,
})
return queueToggle(false)
}, [did, queueToggle])
}, [queryClient, did, queueToggle])
return [queueFollow, queueUnfollow]
}
@@ -269,6 +271,7 @@ function useProfileUnfollowMutation(
export function useProfileMuteMutationQueue(
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
) {
const queryClient = useQueryClient()
const did = profile.did
const initialMuted = profile.viewer?.muted
const muteMutation = useProfileMuteMutation()
@@ -291,25 +294,25 @@ export function useProfileMuteMutationQueue(
},
onSuccess(finalMuted) {
// finalize
updateProfileShadow(did, {muted: finalMuted})
updateProfileShadow(queryClient, did, {muted: finalMuted})
},
})
const queueMute = useCallback(() => {
// optimistically update
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
muted: true,
})
return queueToggle(true)
}, [did, queueToggle])
}, [queryClient, did, queueToggle])
const queueUnmute = useCallback(() => {
// optimistically update
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
muted: false,
})
return queueToggle(false)
}, [did, queueToggle])
}, [queryClient, did, queueToggle])
return [queueMute, queueUnmute]
}
@@ -341,6 +344,7 @@ function useProfileUnmuteMutation() {
export function useProfileBlockMutationQueue(
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
) {
const queryClient = useQueryClient()
const did = profile.did
const initialBlockingUri = profile.viewer?.blocking
const blockMutation = useProfileBlockMutation()
@@ -366,7 +370,7 @@ export function useProfileBlockMutationQueue(
},
onSuccess(finalBlockingUri) {
// finalize
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
blockingUri: finalBlockingUri,
})
},
@@ -374,19 +378,19 @@ export function useProfileBlockMutationQueue(
const queueBlock = useCallback(() => {
// optimistically update
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
blockingUri: 'pending',
})
return queueToggle(true)
}, [did, queueToggle])
}, [queryClient, did, queueToggle])
const queueUnblock = useCallback(() => {
// optimistically update
updateProfileShadow(did, {
updateProfileShadow(queryClient, did, {
blockingUri: undefined,
})
return queueToggle(false)
}, [did, queueToggle])
}, [queryClient, did, queueToggle])
return [queueBlock, queueUnblock]
}
@@ -406,13 +410,14 @@ function useProfileBlockMutation() {
},
onSuccess(_, {did}) {
queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
resetProfilePostsQueries(did, 1000)
resetProfilePostsQueries(queryClient, did, 1000)
},
})
}
function useProfileUnblockMutation() {
const {currentAccount} = useSession()
const queryClient = useQueryClient()
return useMutation<void, Error, {did: string; blockUri: string}>({
mutationFn: async ({blockUri}) => {
if (!currentAccount) {
@@ -425,7 +430,7 @@ function useProfileUnblockMutation() {
})
},
onSuccess(_, {did}) {
resetProfilePostsQueries(did, 1000)
resetProfilePostsQueries(queryClient, did, 1000)
},
})
}
+6 -11
View File
@@ -4,7 +4,6 @@ import {
BSKY_LABELER_DID,
BskyAgent,
} from '@atproto/api'
import {useQueryClient} from '@tanstack/react-query'
import {jwtDecode} from 'jwt-decode'
import {track} from '#/lib/analytics/analytics'
@@ -178,7 +177,6 @@ function createPersistSessionHandler(
}
export function Provider({children}: React.PropsWithChildren<{}>) {
const queryClient = useQueryClient()
const isDirty = React.useRef(false)
const [state, setState] = React.useState<SessionState>({
isInitialLoad: true,
@@ -211,12 +209,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const clearCurrentAccount = React.useCallback(() => {
logger.warn(`session: clear current account`)
__globalAgent = PUBLIC_BSKY_AGENT
queryClient.clear()
setStateAndPersist(s => ({
...s,
currentAccount: undefined,
}))
}, [setStateAndPersist, queryClient])
}, [setStateAndPersist])
const createAccount = React.useCallback<ApiContext['createAccount']>(
async ({
@@ -286,14 +283,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
)
__globalAgent = agent
queryClient.clear()
upsertAccount(account)
logger.debug(`session: created account`, {}, logger.DebugContext.session)
track('Create Account')
logEvent('account:create:success', {})
},
[upsertAccount, queryClient, clearCurrentAccount],
[upsertAccount, clearCurrentAccount],
)
const login = React.useCallback<ApiContext['login']>(
@@ -334,7 +330,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
__globalAgent = agent
// @ts-ignore
if (IS_DEV && isWeb) window.agent = agent
queryClient.clear()
upsertAccount(account)
logger.debug(`session: logged in`, {}, logger.DebugContext.session)
@@ -342,7 +337,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
track('Sign In', {resumedSession: false})
logEvent('account:loggedIn', {logContext, withPassword: true})
},
[upsertAccount, queryClient, clearCurrentAccount],
[upsertAccount, clearCurrentAccount],
)
const logout = React.useCallback<ApiContext['logout']>(
@@ -373,6 +368,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
persistSession: createPersistSessionHandler(
account,
({expired, refreshedAccount}) => {
console.log('PERSIST CALLBACK', refreshedAccount.did)
upsertAccount(refreshedAccount, expired)
},
{networkErrorCallback: clearCurrentAccount},
@@ -411,7 +407,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
agent.session = prevSession
__globalAgent = agent
queryClient.clear()
upsertAccount(account)
if (prevSession.deactivated) {
@@ -447,8 +442,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
try {
const freshAccount = await resumeSessionWithFreshAccount()
console.log('RESUMED SESSION', freshAccount.did)
__globalAgent = agent
queryClient.clear()
upsertAccount(freshAccount)
} catch (e) {
/*
@@ -489,7 +484,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
}
}
},
[upsertAccount, queryClient, clearCurrentAccount],
[upsertAccount, clearCurrentAccount],
)
const resumeSession = React.useCallback<ApiContext['resumeSession']>(
+32 -30
View File
@@ -1,6 +1,5 @@
import React, {useMemo} from 'react'
import {StyleSheet} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {
AppBskyActorDefs,
moderateProfile,
@@ -9,36 +8,38 @@ import {
} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {CenteredView} from '../com/util/Views'
import {ListRef} from '../com/util/List'
import {ScreenHider} from '#/components/moderation/ScreenHider'
import {ProfileLists} from '../com/lists/ProfileLists'
import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {FAB} from '../com/util/fab/FAB'
import {s, colors} from 'lib/styles'
import {useAnalytics} from 'lib/analytics/analytics'
import {ComposeIcon2} from 'lib/icons'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {combinedDisplayName} from 'lib/strings/display-names'
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useProfileQuery} from '#/state/queries/profile'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession, getAgent} from '#/state/session'
import {useModerationOpts} from '#/state/queries/preferences'
import {useLabelerInfoQuery} from '#/state/queries/labeler'
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {cleanError} from '#/lib/strings/errors'
import {useComposerControls} from '#/state/shell/composer'
import {listenSoftReset} from '#/state/events'
import {isInvalidHandle} from '#/lib/strings/handles'
import {useFocusEffect} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {cleanError} from '#/lib/strings/errors'
import {isInvalidHandle} from '#/lib/strings/handles'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {listenSoftReset} from '#/state/events'
import {useLabelerInfoQuery} from '#/state/queries/labeler'
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileQuery} from '#/state/queries/profile'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {getAgent, useSession} from '#/state/session'
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {useComposerControls} from '#/state/shell/composer'
import {useAnalytics} from 'lib/analytics/analytics'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {ComposeIcon2} from 'lib/icons'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {combinedDisplayName} from 'lib/strings/display-names'
import {colors, s} from 'lib/styles'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
import {ScreenHider} from '#/components/moderation/ScreenHider'
import {ProfileFeedgens} from '../com/feeds/ProfileFeedgens'
import {ProfileLists} from '../com/lists/ProfileLists'
import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {FAB} from '../com/util/fab/FAB'
import {ListRef} from '../com/util/List'
import {CenteredView} from '../com/util/Views'
interface SectionRef {
scrollToTop: () => void
@@ -48,6 +49,7 @@ type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
export function ProfileScreen({route}: Props) {
const {_} = useLingui()
const {currentAccount} = useSession()
const queryClient = useQueryClient()
const name =
route.params.name === 'me' ? currentAccount?.did : route.params.name
const moderationOpts = useModerationOpts()
@@ -78,9 +80,9 @@ export function ProfileScreen({route}: Props) {
// When we open the profile, we want to reset the posts query if we are blocked.
React.useEffect(() => {
if (resolvedDid && profile?.viewer?.blockedBy) {
resetProfilePostsQueries(resolvedDid)
resetProfilePostsQueries(queryClient, resolvedDid)
}
}, [profile?.viewer?.blockedBy, resolvedDid])
}, [queryClient, profile?.viewer?.blockedBy, resolvedDid])
// Most pushes will happen here, since we will have only placeholder data
if (isLoadingDid || isLoadingProfile) {