I want to display a loader while a UseLiveQuery is not finished and display a "not found component" if the UseLiveQuery has returned an empty array.
Is there a simple way to do that?
Thank you very much.
I want to display a loader while a UseLiveQuery is not finished and display a "not found component" if the UseLiveQuery has returned an empty array.
Is there a simple way to do that?
Thank you very much.
It's easiest when your querier returns an array (see below). Then a result of undefined means still loading, while an empty array means finished loading but with zero results.
const friends = useLiveQuery (() => db.friends.toArray());
if (!friends) return <Spinner />; // Still loading
return <ul>{
friends.map(friend => <li key={friend.id}>
{friend.name}, {friend.age}
</li>)
}</ul>;
But if your querier returns the result from table.get(), a value of undefined could both mean that query is still loading or that the requested id was not found. To distinguish between the two, you could let the querier return something that makes it easier to distinguish between the two. For example, let the querier return the result as an item in an array and supply a third argument to useLiveQuery() with an empty array (something else than undefined) as a default result.
const [friend, loaded] = useLiveQuery (
() => db.friends.get(1).then(friend => [friend, true]),
[], // deps...
[] // default result: makes 'loaded' undefined while loading
);
if (!loaded) return <Spinner />; // Still loading...
if (!friend) return <NotFound ... />; // Loaded but friend not found
return <>{friend.name}, {friend.age}</>; // Loaded and friend found.
That said, you'll normally not need a spinner when querying indexedDB (unless for very complex queries). In this case, probably better to just return null
instead of <Spinner />
, to bother the end-user the least.
I created a wrapper (React) hook to provide this kind of behaviour with Dexie.js. It is fully typed using TypeScript. It also accounts for missing data (for example, if you .get() using a key not present in IndexedDB) and has a different return type when handling arrays (because in that case we can never have an undefined data result).
import {MySubClassedDexie, useDexieDb} from "@/providers/dexieProvider/dexieProvider";
import {useLiveQuery} from "dexie-react-hooks";
export enum Status {
PENDING = 'pending',
RESOLVED = 'resolved'
}
export type AsyncLiveQueryReturn<T = any> = AsyncLiveQueryReturnPending | AsyncLiveQueryReturnResolved<T>;
export interface AsyncLiveQueryReturnPending {
isLoading: true;
isSuccess: false;
status: Status.PENDING;
data: null;
}
export interface AsyncLiveQueryReturnResolved<T = any> {
isLoading: false;
isSuccess: true;
status: Status.RESOLVED;
data: T extends Array<infer U> ? Array<U> : (T | undefined);
}
const useAsyncLiveQuery = <T>(querier: (db: MySubClassedDexie) => Promise<T>, deps: any[] = [], defaultIfMissing?: T): AsyncLiveQueryReturn<T> => {
const db = useDexieDb();
const [data, status] = useLiveQuery(() => {
return querier(db).then((data: T) => {
const d = data === undefined ? defaultIfMissing : data;
return [d, Status.RESOLVED]
})
}, deps, [null, Status.PENDING])
return {
isLoading: status === Status.PENDING,
isSuccess: status === Status.RESOLVED,
status,
data,
} as AsyncLiveQueryReturn<T>;
};
export default useAsyncLiveQuery;
You use it as follows:
const {isLoading, isSuccess, data} = useAsyncLiveQuery(db => db.items.get(myId), [myId], myFallbackObject);
I hope this helps somebody!