Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): clear all cache when user change #198

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions apps/mobile/app/(app)/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import { useLocale } from '@/locales/provider'
import { useAuth } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useQueryClient } from '@tanstack/react-query'
import { LinearGradient } from 'expo-linear-gradient'
import { Link } from 'expo-router'
import {
Expand All @@ -40,7 +38,6 @@ export default function SettingsScreen() {
const { i18n } = useLingui()
const { language } = useLocale()
const { colorScheme } = useColorScheme()
const queryClient = useQueryClient()

return (
<View className="bg-card">
Expand Down Expand Up @@ -189,8 +186,6 @@ export default function SettingsScreen() {
text: t(i18n)`Sign out`,
style: 'destructive',
onPress: async () => {
await AsyncStorage.clear()
queryClient.clear()
await signOut()
},
},
Expand Down
47 changes: 25 additions & 22 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useWarmUpBrowser } from '@/hooks/use-warm-up-browser'
import { useColorScheme } from '@/hooks/useColorScheme'
import { queryClient } from '@/lib/client'
import { LocaleProvider } from '@/locales/provider'
import { StoreProvider } from '@/stores/store-provider'
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import AsyncStorage from '@react-native-async-storage/async-storage'
import NetInfo from '@react-native-community/netinfo'
Expand Down Expand Up @@ -147,28 +148,30 @@ function RootLayout() {
client={queryClient}
persistOptions={{ persister: asyncStoragePersister }}
>
<LocaleProvider>
<ThemeProvider
value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<SafeAreaProvider>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
name="(aux)"
options={{
presentation: 'modal',
}}
/>
</Stack>
<ToastRoot />
<PortalHost />
</BottomSheetModalProvider>
</GestureHandlerRootView>
</SafeAreaProvider>
</ThemeProvider>
</LocaleProvider>
<StoreProvider>
<LocaleProvider>
<ThemeProvider
value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}
>
<SafeAreaProvider>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
name="(aux)"
options={{
presentation: 'modal',
}}
/>
</Stack>
<ToastRoot />
<PortalHost />
</BottomSheetModalProvider>
</GestureHandlerRootView>
</SafeAreaProvider>
</ThemeProvider>
</LocaleProvider>
</StoreProvider>
</PersistQueryClientProvider>
</ClerkLoaded>
</ClerkProvider>
Expand Down
14 changes: 14 additions & 0 deletions apps/mobile/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { type ClassValue, clsx } from 'clsx'
import { Platform } from 'react-native'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
Expand All @@ -8,3 +10,15 @@ export function cn(...inputs: ClassValue[]) {
export async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

export async function clearAsyncStorage() {
const asyncStorageKeys = await AsyncStorage.getAllKeys()
if (asyncStorageKeys.length > 0) {
if (Platform.OS === 'android') {
await AsyncStorage.clear()
}
if (Platform.OS === 'ios') {
await AsyncStorage.multiRemove(asyncStorageKeys)
}
}
}
2 changes: 1 addition & 1 deletion apps/mobile/stores/budget/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const useBudgetList = () => {

const query = useQuery({
...budgetQueries.all({ setBudgetsState }),
initialData: budgets,
initialData: budgets.length > 0 ? budgets : undefined,
})

const {
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/stores/budget/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type BudgetItem = Budget & {

interface BudgetStore {
budgets: BudgetItem[]
_reset: () => void
setBudgets: (budgets: BudgetItem[]) => void
updateBudget: (budget: BudgetItem) => void
}
Expand All @@ -17,6 +18,8 @@ export const useBudgetStore = create<BudgetStore>()(
persist(
(set) => ({
budgets: [],
// biome-ignore lint/style/useNamingConvention: <explanation>
_reset: () => set({ budgets: [] }),
setBudgets: (budgets: BudgetItem[]) => set({ budgets }),
updateBudget: (budget: BudgetItem) =>
set((state) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/stores/category/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const useCategoryList = () => {

const query = useQuery({
...categoryQueries.all({ setCategoriesState }),
initialData: categories,
initialData: categories?.length > 0 ? categories : undefined,
})

const { categoriesDict, incomeCategories, expenseCategories } =
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/stores/category/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createJSONStorage, persist } from 'zustand/middleware'

interface CategoryStore {
categories: Category[]
_reset: () => void
setCategories: (categories: Category[]) => void
updateCategory: (category: Category) => void
}
Expand All @@ -18,6 +19,8 @@ export const useCategoryStore = create<CategoryStore>()(
persist(
(set) => ({
categories: [],
// biome-ignore lint/style/useNamingConvention: <explanation>
_reset: () => set({ categories: [] }),
setCategories: (categories: Category[]) =>
set({ categories: normalizeCategories(categories) }),
updateCategory: (category: Category) =>
Expand Down
64 changes: 64 additions & 0 deletions apps/mobile/stores/store-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { clearAsyncStorage } from '@/lib/utils'
import { useAuth } from '@clerk/clerk-expo'
import { useAsyncStorage } from '@react-native-async-storage/async-storage'
import { useQueryClient } from '@tanstack/react-query'
import {
type FC,
type ReactNode,
useCallback,
useEffect,
useState,
} from 'react'
import { useResetAllStores } from './use-reset-all-stores'

export type StoreProviderProps = {
children: ReactNode
}

export const StoreProvider: FC<StoreProviderProps> = ({ children }) => {
const [isReady, setIsReady] = useState(false)
const { userId } = useAuth()
const queryClient = useQueryClient()
const { getItem, setItem, removeItem } = useAsyncStorage('user-id')
const resetAllStores = useResetAllStores()

const handleUserChange = useCallback(async () => {
const storedUserId = await getItem()

if (storedUserId === userId) {
return
}

// biome-ignore lint/suspicious/noConsoleLog: <explanation>
console.log('User changed, clearing storage', userId)

await clearAsyncStorage()
queryClient.clear()
queryClient.invalidateQueries()
resetAllStores()

// biome-ignore lint/suspicious/noConsoleLog: <explanation>
console.log('Storage cleared')

if (userId) {
await setItem(userId)
} else {
await removeItem()
}
}, [getItem, queryClient, userId, removeItem, setItem, resetAllStores])

// Clear the async storage & queryClient when the user changes
useEffect(() => {
handleUserChange().catch((error) => {
console.error('Failed to clear storage', error)
})

setIsReady(true)
}, [handleUserChange])

if (!isReady) {
return null
}

return children
}
7 changes: 5 additions & 2 deletions apps/mobile/stores/transaction/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ export function useTransactionList({
filters: { from, to },
updateTransactionsByRangeInStore: updateTransactionsByRange,
}),
initialData: transactionsInRangeFromStore,
initialData:
transactionsInRangeFromStore.length > 0
? transactionsInRangeFromStore
: undefined,
})

const { transactions, transactionDict, totalExpense, totalIncome } =
Expand Down Expand Up @@ -98,7 +101,7 @@ export function useTransaction(transactionId: string) {
updateTransactionInStore: updateTransaction,
removeTransactionInStore: removeTransaction,
}),
initialData: transaction,
initialData: transaction || undefined,
})

return { ...query, transaction }
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/stores/transaction/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Transaction = TransactionPopulated

export interface TransactionStore {
transactions: Transaction[]
_reset: () => void
setTransactions: (transactions: Transaction[]) => void
addTransactions: (transactions: Transaction[]) => void
updateTransactionsByRange: (args: {
Expand All @@ -31,6 +32,8 @@ export const useTransactionStore = create<TransactionStore>()(
persist(
(set) => ({
transactions: [],
// biome-ignore lint/style/useNamingConvention: <explanation>
_reset: () => set({ transactions: [] }),
setTransactions: (transactions) => {
set({ transactions: normalizeTransactions(transactions) })
},
Expand Down
18 changes: 18 additions & 0 deletions apps/mobile/stores/use-reset-all-stores.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useCallback } from 'react'
import { useBudgetStore } from './budget/store'
import { useCategoryStore } from './category/store'
import { useTransactionStore } from './transaction/store'

export const useResetAllStores = () => {
const resetBudgetStore = useBudgetStore((state) => state._reset)
const resetCategoryStore = useCategoryStore((state) => state._reset)
const resetTransactionStore = useTransactionStore((state) => state._reset)

const resetAllStores = useCallback(() => {
resetBudgetStore()
resetCategoryStore()
resetTransactionStore()
}, [resetBudgetStore, resetCategoryStore, resetTransactionStore])

return resetAllStores
}