import { useConvex } from "convex/react" import { getFunctionName, type FunctionReference, type FunctionReturnType, type OptionalRestArgs, } from "convex/server" import { LRUCache } from "lru-cache" import { useEffect, useState } from "react" import { useMemoValue } from "./useMemoValue" // LRU means "last recently used" // this is basically a fancy Map with a limited capacity of 100 items (or however many you want) // for the sake of saving memory const cache = new LRUCache>({ max: 100, }) function getCacheKey( query: FunctionReference<"query">, args: [args?: Record], ) { // JSON.stringify is basic and misses some edge cases, // but is more than good enough for simple cases // you might consider a stable stringifier or something like superjson instead return JSON.stringify([getFunctionName(query), args]) } function getQueryCacheData>( query: Query, args: OptionalRestArgs, ) { return cache.get(getCacheKey(query, args)) as | FunctionReturnType | undefined } function setQueryCacheData>( query: Query, args: OptionalRestArgs, data: FunctionReturnType, ) { cache.set(getCacheKey(query, args), data) } export function useQuerySuspense>( query: Query, ...args: OptionalRestArgs ) { const convex = useConvex() const cacheData = getQueryCacheData(query, args) if (cacheData === undefined) { throw new Promise((resolve) => { const watch = convex.watchQuery(query, ...args) const result = watch.localQueryResult() if (result !== undefined) { setQueryCacheData(query, args, result) resolve() return } const unsubscribe = watch.onUpdate(() => { const result = watch.localQueryResult() if (result === undefined) { throw new Error("No query result") } setQueryCacheData(query, args, result) resolve() unsubscribe() }) }) } const [data, setData] = useState(cacheData) // useMemoValue makes these arguments stable for useEffect const memoQuery = useMemoValue( query, (a, b) => getFunctionName(a) === getFunctionName(b), ) const memoArgs = useMemoValue(args) useEffect(() => { const watch = convex.watchQuery(memoQuery, ...memoArgs) return watch.onUpdate(() => { const result = watch.localQueryResult() if (result === undefined) { throw new Error("No query result") } setData(result) setQueryCacheData(memoQuery, memoArgs, result) }) }, [convex, memoQuery, memoArgs]) return data }