Replies: 6 comments 7 replies
-
|
isLoading: The third argument to useLiveQuery() is a default value returned while loading. With IndexedDB, loading time is normally too short to make it feasible with fallback rendering. I normally just return null from the component while loading. Examples: export function FriendListComponent() {
// When returning array, we can rely on the default and do not need to specify 3rd arg:
const friends = useLiveQuery(() => db.friends.toArray());
// A falsy result means we're still loading
if (!friends) return null; // Initial render (is loading)
return <ul>{
friends.map(friend => <li key={friend.id}>{friend.name}</li>)
}</ul>;
}
export function FriendDetailsComponent(friendId) {
const friend = useLiveQuery(
() => db.friends.get(friendId), // This query may return undefined!
[friendId], // Deps
null // Specify fallback distinguished from not-found result.
);
if (friend === null) return null; // Initial render (is loading)
if (friend === undefined) return <p>Friend not found</p>; // Not found.
return <div>
<p>Name: {friend.name}</p>
<p>Age: {friend.age}</p>
</div>;
}Suspense support: We might support a suspense based useLiveQuery in future. That would require a DBCore caching middleware that is capable of caching all responses including IDBCoreCursors - which in turn would require a virtual cursor ("CachedCursor" or how to name it) to be implemented that will behave just like the original cursor. So, it's doable but not super straightforward to implement. |
Beta Was this translation helpful? Give feedback.
-
|
The default return worked well for me. I used a |
Beta Was this translation helpful? Give feedback.
-
|
Here's my approach for adding Suspense support to // utils/useSuspenseLiveQuery.ts
import { liveQuery } from 'dexie';
import { type Atom, useAtomValue } from 'jotai';
import { atomWithObservable } from 'jotai/utils';
import { useRef } from 'react';
// Must use a global cache because Suspense breaks the rules of hooks like
// useMemo and useRef
// https://github.com/facebook/react/issues/15137
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-redundant-type-constituents
const CACHE = new Map<string, Atom<any | Promise<any>> | undefined>();
// Cache for 5 minutes, which was inspired by TanStack Query
const CACHE_ITEM_LIFESPAN_MS = 1000 * 60 * 5;
type CacheKey = string | number;
// Force at least one cache key past the `use` prefixed key to ensure that
// using useSuspenseLiveQuery is necessary vs a global atom.
type CacheKeys<T = CacheKey> = [`use${string}`, T, ...T[]];
type InitialValue<Data> = Data | (() => Data);
// A suspense-enabled version of useLiveQuery from Dexie
export default function useSuspenseLiveQuery<T>(
querier: () => Promise<T> | T,
cacheKeys: CacheKeys,
): T | undefined;
export default function useSuspenseLiveQuery<T, U extends InitialValue<T>>(
querier: () => Promise<T> | T,
cacheKeys: CacheKeys,
initialValue: U,
): T | U;
export default function useSuspenseLiveQuery<T, U extends InitialValue<T>>(
querier: () => Promise<T> | T,
cacheKeys: CacheKeys,
initialValue?: U,
): T | U {
const cacheKey = cacheKeys.join(':');
const atomRef = useRef<[string, Atom<T | Promise<T>> | undefined]>([
cacheKey,
CACHE.get(cacheKey),
]);
// Look for atom in cache
if (atomRef.current[0] !== cacheKey) {
atomRef.current = [cacheKey, CACHE.get(cacheKey)];
}
// Atom not in cache
if (!atomRef.current[1]) {
const newAtom = atomWithObservable<T>(
() => liveQuery(querier),
initialValue ? { initialValue } : undefined,
);
if (process.env.NODE_ENV === 'development') {
newAtom.debugLabel = cacheKey;
}
atomRef.current = [cacheKey, newAtom];
CACHE.set(cacheKey, newAtom);
// Remove from cache after a timeout to allow for garbage collection
setTimeout(() => {
CACHE.delete(cacheKey);
}, CACHE_ITEM_LIFESPAN_MS);
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return useAtomValue(atomRef.current[1]!);
}Example Use// store/sessions.ts
export function useSession(sessionId: SessionId) {
return useSuspenseLiveQuery(
() => db.sessions.get(sessionId),
['useSession', sessionId],
);
} |
Beta Was this translation helpful? Give feedback.
-
|
@dfahlander Now that view transitions are stable this is even more important. Without suspense there is a transition to the empty state and then the data appears only after the view transition finishes. This only makes things worse because normally the empty state shows only for split second. Ideally there would be no empty state visible at all. |
Beta Was this translation helpful? Give feedback.
-
|
I made a WIP PR: #2205 |
Beta Was this translation helpful? Give feedback.
-
|
Not sure if it's related to this, but I haven't been able to find a clean way to use dexie promise api with the new use function in react. Using a memo on the promise doesn't seem to be stable enough. Edit: the reason why I ask is it seems having "suspense enabled data fetch" is handy with the new Activity component in react 19.2. We already use useLiveQuery heavily... |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, I see there is no property like
isLoadingforuseLiveQueryresult.I wonder if it is possible to adopt
SuspenseforuseLiveQuery.Beta Was this translation helpful? Give feedback.
All reactions