Reputation: 33
I got the data stored in indexedDB inside the fetcher of useSWR. Then, the memory usage increased every time auto refresh occurred.
When I got the data stored in indexedDB only once from outside of fetcher and passed it to the fetcher instead of getting it inside the fetcher, the memory usage did not increase.
Please tell me why a memory leak occurs when I get the data stored in indexedDB inside the fetcher.
I used idb with the indexedDB wrapper package.
package version
react: 18.2.0
next: 14.0.1
node.js: 20.18.0
swr: 2.2.4
idb: 8.0.0
// indexedDB.ts
import { DBSchema, IDBPDatabase, openDB } from 'idb'
import { StoreKey, StoreNames, StoreValue } from 'idb/build/entry'
import { Data } from './types'
const DB_NAME = 'temp-indexed-database'
// Define database schema
interface IndexedDB extends DBSchema {
myData: {
key: string
value: Data
indexes: {
transactTime: string
}
}
}
const getDB = (() => {
let dbPromise: Promise<IDBPDatabase<IndexedDB>> | null = null
return async () => {
if (typeof window === 'undefined') return null
if (!dbPromise) {
dbPromise = openDB<IndexedDB>(DB_NAME)
}
return dbPromise
}
})()
export const getAllData = async <S extends StoreNames<IndexedDB>>(
storeName: S,
query?: StoreKey<IndexedDB, S> | IDBKeyRange | null,
count?: number
) => {
const db = await getDB()
if (!db) return []
return db.getAll(storeName, query, count)
}
export const addData = async <S extends StoreNames<IndexedDB>>(
storeName: S,
data: StoreValue<IndexedDB, S> | StoreValue<IndexedDB, S>[],
key?: StoreKey<IndexedDB, S> | IDBKeyRange
) => {
const db = await getDB()
if (!db) return
const transaction = db.transaction(storeName, 'readwrite')
const store = transaction.objectStore(storeName)
if (Array.isArray(data)) {
await Promise.all(data.map((item) => store.put(item, key)))
} else {
await store.put(data, key)
}
}
// hook.ts
import useSWR from 'swr'
import { Data } from './types'
export const useSWRData = (params) => {
const [initialData, setInitialData] = useState<Data[]>(
[]
)
useEffect(() => {
getAllData('myData').then(setInitialData)
}, [])
const {
data,
isValidating,
error,
} = useSWR<Data[]>(
{ key: 'fetchData', params },
fetchAndSyncData
)
return useMemo(
() => ({
data: data ?? initialData,
isValidating,
error,
}),
[data, initialData, isValidating, error]
)
}
// service.ts
export const fetchAndSyncData = async (params: {params: AnyRecord}) => {
try {
const indexedData= await getAllData('myData')
const lastDate = getLastDate(indexedData)
const newData = await fetchData({ params, startDate: lastDate })
if (newData.length > 0) {
await addData('myData', newData)
}
return sortObjArrByKey(
[...(newData ?? []), ...(indexedData ?? [])],
'timestamp'
)
} catch (error) {
console.error('Error fetching and syncing data:', error)
return []
}
}
Upvotes: 2
Views: 132
Reputation: 160
In the fetchAndSyncData
function, you call getAllData
every time the fetcher runs. Since useSWR
revalidates data periodically (auto refreshes), each fetcher call triggers a new promise to access IndexedDB
, leading to increased memory usage over time.
You can update your code for handle your getAllData
function:
export const useSWRData = (params) => {
const [initialData, setInitialData] = useState<Data[]>([])
useEffect(() => {
// Fetch IndexedDB data once, outside the fetcher
getAllData('myData').then(setInitialData)
}, [])
const { data, isValidating, error } = useSWR<Data[]>(
{ key: 'fetchData', params, initialData }, // Pass initial data to fetcher
fetchAndSyncData
)
return useMemo(() => ({
data: data ?? initialData, // Use initialData if no fresh data
isValidating,
error,
}), [data, initialData, isValidating, error])
}
Now refactor your fetcher:
export const fetchAndSyncData = async ({ params, initialData }: { params: AnyRecord, initialData: Data[] }) => {
try {
const lastDate = getLastDate(initialData) // Use pre-fetched IndexedDB data
const newData = await fetchData({ params, startDate: lastDate })
if (newData.length > 0) {
await addData('myData', newData) // Sync new data to IndexedDB
}
// Combine new and old data
return sortObjArrByKey([...newData, ...initialData], 'timestamp')
} catch (error) {
console.error('Error fetching and syncing data:', error)
return initialData // Return cached data if fetch fails
}
}
Upvotes: 1