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

Async Loading Status #2089

Open
ericdudley opened this issue Oct 30, 2024 · 2 comments
Open

Async Loading Status #2089

ericdudley opened this issue Oct 30, 2024 · 2 comments

Comments

@ericdudley
Copy link

Hello, I am using Dexie.js for the first time with my Svelte5 app, and I am trying to figure out how to show the query async status lifecycle.

	let transactions = $derived.by(() => {
		// noop just to make it reactive
		prefix;
		startDate;
		endDate;

		return liveQuery(() =>
			db.tx
				.where('yyyyMMDd')
				.between(format(startDate, 'yyyy-MM-dd'), format(endDate, 'yyyy-MM-dd'), true, true)
				.and((tx) => !!tx.label?.startsWith(prefix ?? ''))
				.sortBy('yyyyMMDd')
		);
	});

For the initial load, I can check for transactions or $transactions being undefined, but after I change the query inputs I would like to show some "loading" UI while the new query is running. Likewise, if an error occurs, I would like to be able to show that.

What is the best practice for showing these intermediate loading + error states?

@dfahlander
Copy link
Collaborator

dfahlander commented Oct 30, 2024

liveQuery() does not emit anything until a query finish. If arguments change and you gain a new store, Svelte will keep showing the value from the previous store until the new store emits something. For most cases this is good because it could otherwise have been causing unnecessary flickering. liveQuery emits errors according to the TC39 Observable proposal but Svelte stores does not consume errors.

The following helper should give what you need (not tested):

export function svelteLiveQuery<T>(querier: () => Promise<T>): Readable<QueryResult<T>> {
  return {
    subscribe(emit) {
      let current: QueryResult<T> = {
        value: null,
        error: null,
        isLoading: true,
      };
      emit(current); // immediately emit initial value before loading
      const subscription = liveQuery(querier).subscribe(
        (value) => {
          current.isLoading = false;
          current.error = null;
          current.value = value;
          emit(current);
        },
        (error) => {
          current.isLoading = false;
          current.error = error;
          emit(current);
        }
      );
      return () => subscription.unsubscribe();
    },
  };
}

interface QueryResult<T> {
  value: T | null;
  error: any;
  isLoading: boolean;
}

interface Readable<T> {
  subscribe(this: void, subscriber: (current: T) => void): () => void;
}

To use it:

<script>
  let transactions = $derived.by(() => {
		// noop just to make it reactive
		prefix;
		startDate;
		endDate;

		return svelteLiveQuery(() =>
			db.tx
				.where('yyyyMMDd')
				.between(format(startDate, 'yyyy-MM-dd'), format(endDate, 'yyyy-MM-dd'), true, true)
				.and((tx) => !!tx.label?.startsWith(prefix ?? ''))
				.sortBy('yyyyMMDd')
		);
	});
</script>

{#if $transactions.isLoading}
   <p>Loading...</p>
{/if}
{#if $transactions.error}
   <p>Error: $transactions.error</p>
{/if}
{#if $transactions.value}
<ul>
  {#each $transactions.value as tx (tx.id)}
    <li> ... </li>
  {/each}
</ul>
{/if}

There might be some even better helper (such as a liveQuery rune or something) that would feel more native to Svelte. I'm not a Svelte expert but any contribution would be welcome.

@dfahlander
Copy link
Collaborator

Note: I just updated the my code snippet with type annotations and renamed subscriber to emit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants