Scope query client per DID (#3333)
* Move QueryProvider inside the key * Pull useQueryClient-dependent code down in App.native * Remove useQueryClient dependency from session provider * Scope query client per DIDzio/stable
parent
db3cd3e821
commit
e51ccb46b8
|
@ -54,12 +54,10 @@ SplashScreen.preventAutoHideAsync()
|
||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const {isInitialLoad, currentAccount} = useSession()
|
const {isInitialLoad, currentAccount} = useSession()
|
||||||
const {resumeSession} = useSessionApi()
|
const {resumeSession} = useSessionApi()
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const theme = useColorModeTheme()
|
const theme = useColorModeTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
useIntentHandler()
|
useIntentHandler()
|
||||||
useNotificationsListener(queryClient)
|
|
||||||
useOTAUpdates()
|
useOTAUpdates()
|
||||||
|
|
||||||
// init
|
// init
|
||||||
|
@ -79,25 +77,29 @@ function InnerApp() {
|
||||||
<React.Fragment
|
<React.Fragment
|
||||||
// Resets the entire tree below when it changes:
|
// Resets the entire tree below when it changes:
|
||||||
key={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<LabelDefsProvider>
|
<PushNotificationsListener>
|
||||||
<LoggedOutViewProvider>
|
<StatsigProvider>
|
||||||
<SelectedFeedProvider>
|
<LabelDefsProvider>
|
||||||
<UnreadNotifsProvider>
|
<LoggedOutViewProvider>
|
||||||
<ThemeProvider theme={theme}>
|
<SelectedFeedProvider>
|
||||||
{/* All components should be within this provider */}
|
<UnreadNotifsProvider>
|
||||||
<RootSiblingParent>
|
<ThemeProvider theme={theme}>
|
||||||
<GestureHandlerRootView style={s.h100pct}>
|
{/* All components should be within this provider */}
|
||||||
<TestCtrls />
|
<RootSiblingParent>
|
||||||
<Shell />
|
<GestureHandlerRootView style={s.h100pct}>
|
||||||
</GestureHandlerRootView>
|
<TestCtrls />
|
||||||
</RootSiblingParent>
|
<Shell />
|
||||||
</ThemeProvider>
|
</GestureHandlerRootView>
|
||||||
</UnreadNotifsProvider>
|
</RootSiblingParent>
|
||||||
</SelectedFeedProvider>
|
</ThemeProvider>
|
||||||
</LoggedOutViewProvider>
|
</UnreadNotifsProvider>
|
||||||
</LabelDefsProvider>
|
</SelectedFeedProvider>
|
||||||
</StatsigProvider>
|
</LoggedOutViewProvider>
|
||||||
|
</LabelDefsProvider>
|
||||||
|
</StatsigProvider>
|
||||||
|
</PushNotificationsListener>
|
||||||
|
</QueryProvider>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</Splash>
|
</Splash>
|
||||||
</Alf>
|
</Alf>
|
||||||
|
@ -105,6 +107,12 @@ function InnerApp() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PushNotificationsListener({children}: {children: React.ReactNode}) {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
useNotificationsListener(queryClient)
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isReady, setReady] = useState(false)
|
const [isReady, setReady] = useState(false)
|
||||||
|
|
||||||
|
@ -121,29 +129,27 @@ function App() {
|
||||||
* that is set up in the InnerApp component above.
|
* that is set up in the InnerApp component above.
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<QueryProvider>
|
<SessionProvider>
|
||||||
<SessionProvider>
|
<ShellStateProvider>
|
||||||
<ShellStateProvider>
|
<PrefsStateProvider>
|
||||||
<PrefsStateProvider>
|
<MutedThreadsProvider>
|
||||||
<MutedThreadsProvider>
|
<InvitesStateProvider>
|
||||||
<InvitesStateProvider>
|
<ModalStateProvider>
|
||||||
<ModalStateProvider>
|
<DialogStateProvider>
|
||||||
<DialogStateProvider>
|
<LightboxStateProvider>
|
||||||
<LightboxStateProvider>
|
<I18nProvider>
|
||||||
<I18nProvider>
|
<PortalProvider>
|
||||||
<PortalProvider>
|
<InnerApp />
|
||||||
<InnerApp />
|
</PortalProvider>
|
||||||
</PortalProvider>
|
</I18nProvider>
|
||||||
</I18nProvider>
|
</LightboxStateProvider>
|
||||||
</LightboxStateProvider>
|
</DialogStateProvider>
|
||||||
</DialogStateProvider>
|
</ModalStateProvider>
|
||||||
</ModalStateProvider>
|
</InvitesStateProvider>
|
||||||
</InvitesStateProvider>
|
</MutedThreadsProvider>
|
||||||
</MutedThreadsProvider>
|
</PrefsStateProvider>
|
||||||
</PrefsStateProvider>
|
</ShellStateProvider>
|
||||||
</ShellStateProvider>
|
</SessionProvider>
|
||||||
</SessionProvider>
|
|
||||||
</QueryProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,25 +54,27 @@ function InnerApp() {
|
||||||
<React.Fragment
|
<React.Fragment
|
||||||
// Resets the entire tree below when it changes:
|
// Resets the entire tree below when it changes:
|
||||||
key={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<LabelDefsProvider>
|
<StatsigProvider>
|
||||||
<LoggedOutViewProvider>
|
<LabelDefsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<UnreadNotifsProvider>
|
<SelectedFeedProvider>
|
||||||
<ThemeProvider theme={theme}>
|
<UnreadNotifsProvider>
|
||||||
{/* All components should be within this provider */}
|
<ThemeProvider theme={theme}>
|
||||||
<RootSiblingParent>
|
{/* All components should be within this provider */}
|
||||||
<SafeAreaProvider>
|
<RootSiblingParent>
|
||||||
<Shell />
|
<SafeAreaProvider>
|
||||||
</SafeAreaProvider>
|
<Shell />
|
||||||
</RootSiblingParent>
|
</SafeAreaProvider>
|
||||||
<ToastContainer />
|
</RootSiblingParent>
|
||||||
</ThemeProvider>
|
<ToastContainer />
|
||||||
</UnreadNotifsProvider>
|
</ThemeProvider>
|
||||||
</SelectedFeedProvider>
|
</UnreadNotifsProvider>
|
||||||
</LoggedOutViewProvider>
|
</SelectedFeedProvider>
|
||||||
</LabelDefsProvider>
|
</LoggedOutViewProvider>
|
||||||
</StatsigProvider>
|
</LabelDefsProvider>
|
||||||
|
</StatsigProvider>
|
||||||
|
</QueryProvider>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</Alf>
|
</Alf>
|
||||||
)
|
)
|
||||||
|
@ -94,29 +96,27 @@ function App() {
|
||||||
* that is set up in the InnerApp component above.
|
* that is set up in the InnerApp component above.
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<QueryProvider>
|
<SessionProvider>
|
||||||
<SessionProvider>
|
<ShellStateProvider>
|
||||||
<ShellStateProvider>
|
<PrefsStateProvider>
|
||||||
<PrefsStateProvider>
|
<MutedThreadsProvider>
|
||||||
<MutedThreadsProvider>
|
<InvitesStateProvider>
|
||||||
<InvitesStateProvider>
|
<ModalStateProvider>
|
||||||
<ModalStateProvider>
|
<DialogStateProvider>
|
||||||
<DialogStateProvider>
|
<LightboxStateProvider>
|
||||||
<LightboxStateProvider>
|
<I18nProvider>
|
||||||
<I18nProvider>
|
<PortalProvider>
|
||||||
<PortalProvider>
|
<InnerApp />
|
||||||
<InnerApp />
|
</PortalProvider>
|
||||||
</PortalProvider>
|
</I18nProvider>
|
||||||
</I18nProvider>
|
</LightboxStateProvider>
|
||||||
</LightboxStateProvider>
|
</DialogStateProvider>
|
||||||
</DialogStateProvider>
|
</ModalStateProvider>
|
||||||
</ModalStateProvider>
|
</InvitesStateProvider>
|
||||||
</InvitesStateProvider>
|
</MutedThreadsProvider>
|
||||||
</MutedThreadsProvider>
|
</PrefsStateProvider>
|
||||||
</PrefsStateProvider>
|
</ShellStateProvider>
|
||||||
</ShellStateProvider>
|
</SessionProvider>
|
||||||
</SessionProvider>
|
|
||||||
</QueryProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React, {useRef, useState} from 'react'
|
||||||
import {AppState, AppStateStatus} from 'react-native'
|
import {AppState, AppStateStatus} from 'react-native'
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
||||||
|
@ -39,31 +39,27 @@ focusManager.setEventListener(onFocus => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const createQueryClient = () =>
|
||||||
defaultOptions: {
|
new QueryClient({
|
||||||
queries: {
|
defaultOptions: {
|
||||||
// NOTE
|
queries: {
|
||||||
// refetchOnWindowFocus breaks some UIs (like feeds)
|
// NOTE
|
||||||
// so we only selectively want to enable this
|
// refetchOnWindowFocus breaks some UIs (like feeds)
|
||||||
// -prf
|
// so we only selectively want to enable this
|
||||||
refetchOnWindowFocus: false,
|
// -prf
|
||||||
// Structural sharing between responses makes it impossible to rely on
|
refetchOnWindowFocus: false,
|
||||||
// "first seen" timestamps on objects to determine if they're fresh.
|
// Structural sharing between responses makes it impossible to rely on
|
||||||
// Disable this optimization so that we can rely on "first seen" timestamps.
|
// "first seen" timestamps on objects to determine if they're fresh.
|
||||||
structuralSharing: false,
|
// Disable this optimization so that we can rely on "first seen" timestamps.
|
||||||
// We don't want to retry queries by default, because in most cases we
|
structuralSharing: false,
|
||||||
// want to fail early and show a response to the user. There are
|
// We don't want to retry queries by default, because in most cases we
|
||||||
// exceptions, and those can be made on a per-query basis. For others, we
|
// want to fail early and show a response to the user. There are
|
||||||
// should give users controls to retry.
|
// exceptions, and those can be made on a per-query basis. For others, we
|
||||||
retry: false,
|
// should give users controls to retry.
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const asyncStoragePersister = createAsyncStoragePersister({
|
|
||||||
storage: AsyncStorage,
|
|
||||||
key: 'queryCache',
|
|
||||||
})
|
|
||||||
|
|
||||||
const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] =
|
const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] =
|
||||||
{
|
{
|
||||||
|
@ -73,12 +69,50 @@ const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehyd
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const persistOptions = {
|
export function QueryProvider({
|
||||||
persister: asyncStoragePersister,
|
children,
|
||||||
dehydrateOptions,
|
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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QueryProvider({children}: {children: React.ReactNode}) {
|
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,
|
||||||
|
}
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<PersistQueryClientProvider
|
<PersistQueryClientProvider
|
||||||
client={queryClient}
|
client={queryClient}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
BSKY_LABELER_DID,
|
BSKY_LABELER_DID,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
|
||||||
import {jwtDecode} from 'jwt-decode'
|
import {jwtDecode} from 'jwt-decode'
|
||||||
|
|
||||||
import {track} from '#/lib/analytics/analytics'
|
import {track} from '#/lib/analytics/analytics'
|
||||||
|
@ -178,7 +177,6 @@ function createPersistSessionHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const isDirty = React.useRef(false)
|
const isDirty = React.useRef(false)
|
||||||
const [state, setState] = React.useState<SessionState>({
|
const [state, setState] = React.useState<SessionState>({
|
||||||
isInitialLoad: true,
|
isInitialLoad: true,
|
||||||
|
@ -211,12 +209,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const clearCurrentAccount = React.useCallback(() => {
|
const clearCurrentAccount = React.useCallback(() => {
|
||||||
logger.warn(`session: clear current account`)
|
logger.warn(`session: clear current account`)
|
||||||
__globalAgent = PUBLIC_BSKY_AGENT
|
__globalAgent = PUBLIC_BSKY_AGENT
|
||||||
queryClient.clear()
|
|
||||||
setStateAndPersist(s => ({
|
setStateAndPersist(s => ({
|
||||||
...s,
|
...s,
|
||||||
currentAccount: undefined,
|
currentAccount: undefined,
|
||||||
}))
|
}))
|
||||||
}, [setStateAndPersist, queryClient])
|
}, [setStateAndPersist])
|
||||||
|
|
||||||
const createAccount = React.useCallback<ApiContext['createAccount']>(
|
const createAccount = React.useCallback<ApiContext['createAccount']>(
|
||||||
async ({
|
async ({
|
||||||
|
@ -286,14 +283,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
)
|
)
|
||||||
|
|
||||||
__globalAgent = agent
|
__globalAgent = agent
|
||||||
queryClient.clear()
|
|
||||||
upsertAccount(account)
|
upsertAccount(account)
|
||||||
|
|
||||||
logger.debug(`session: created account`, {}, logger.DebugContext.session)
|
logger.debug(`session: created account`, {}, logger.DebugContext.session)
|
||||||
track('Create Account')
|
track('Create Account')
|
||||||
logEvent('account:create:success', {})
|
logEvent('account:create:success', {})
|
||||||
},
|
},
|
||||||
[upsertAccount, queryClient, clearCurrentAccount],
|
[upsertAccount, clearCurrentAccount],
|
||||||
)
|
)
|
||||||
|
|
||||||
const login = React.useCallback<ApiContext['login']>(
|
const login = React.useCallback<ApiContext['login']>(
|
||||||
|
@ -334,7 +330,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
__globalAgent = agent
|
__globalAgent = agent
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (IS_DEV && isWeb) window.agent = agent
|
if (IS_DEV && isWeb) window.agent = agent
|
||||||
queryClient.clear()
|
|
||||||
upsertAccount(account)
|
upsertAccount(account)
|
||||||
|
|
||||||
logger.debug(`session: logged in`, {}, logger.DebugContext.session)
|
logger.debug(`session: logged in`, {}, logger.DebugContext.session)
|
||||||
|
@ -342,7 +337,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
track('Sign In', {resumedSession: false})
|
track('Sign In', {resumedSession: false})
|
||||||
logEvent('account:loggedIn', {logContext, withPassword: true})
|
logEvent('account:loggedIn', {logContext, withPassword: true})
|
||||||
},
|
},
|
||||||
[upsertAccount, queryClient, clearCurrentAccount],
|
[upsertAccount, clearCurrentAccount],
|
||||||
)
|
)
|
||||||
|
|
||||||
const logout = React.useCallback<ApiContext['logout']>(
|
const logout = React.useCallback<ApiContext['logout']>(
|
||||||
|
@ -411,7 +406,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
|
|
||||||
agent.session = prevSession
|
agent.session = prevSession
|
||||||
__globalAgent = agent
|
__globalAgent = agent
|
||||||
queryClient.clear()
|
|
||||||
upsertAccount(account)
|
upsertAccount(account)
|
||||||
|
|
||||||
if (prevSession.deactivated) {
|
if (prevSession.deactivated) {
|
||||||
|
@ -448,7 +442,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
try {
|
try {
|
||||||
const freshAccount = await resumeSessionWithFreshAccount()
|
const freshAccount = await resumeSessionWithFreshAccount()
|
||||||
__globalAgent = agent
|
__globalAgent = agent
|
||||||
queryClient.clear()
|
|
||||||
upsertAccount(freshAccount)
|
upsertAccount(freshAccount)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/*
|
/*
|
||||||
|
@ -489,7 +482,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[upsertAccount, queryClient, clearCurrentAccount],
|
[upsertAccount, clearCurrentAccount],
|
||||||
)
|
)
|
||||||
|
|
||||||
const resumeSession = React.useCallback<ApiContext['resumeSession']>(
|
const resumeSession = React.useCallback<ApiContext['resumeSession']>(
|
||||||
|
|
Loading…
Reference in New Issue