sveltelean Query
Version v0.2.0

Example

Open a dialog, close it with Escape before the answer arrives, open it again.

Member details

The example runs in the browser.

Requests the server received

  1. None yet.
Code
<!-- src/lib/members/MemberDialog.svelte -->
<script lang="ts">
	import { createQuery } from '@svelte-lean/query';
	import { getMember } from '$api/members';

	let { id, name }: { id: number; name: string } = $props();

	const uid = $props.id();
	let dialog = $state<HTMLDialogElement>();
	let open = $state(false);
	// Created now, requested only when `member` is read on screen.
	const member = createQuery(getMember, () => ({ id }));

	function show() {
		open = true;
		dialog?.showModal();
	}
</script>

<button type="button" onclick={show}>Details of {name}</button>

<dialog bind:this={dialog} onclose={() => (open = false)} aria-labelledby="{uid}-title">
	<h2 id="{uid}-title">{name}</h2>
	{#if open}
		{#if member.loading}
			<p>Loading…</p>
		{:else if member.error}
			<p role="alert">{member.error.message}</p>
		{:else if member.data}
			<p>{member.data.email} · {member.data.team} · {member.data.status}</p>
		{/if}
	{/if}
	<form method="dialog"><button>Close</button></form>
</dialog>
Three rows, each rendering the component shown. An in-memory server answers after a fixed delay.
src/lib/api/members.ts
export const getMember = defineQuery({
	get: 'api/members/{id}',
	result: type<Member>(),
	entity: 'Member',
	access: 'members.view'
});

How it works

MomentRequestState
The component mountsnone: createQuery creates no entry until it is readinfo.status: 'idle'
The dialog opensGET api/members/1, cause mountloading
Closed before the answeraborted; nothing is writtenunchanged
Opened again, data cachednone while fresh, else a background refetchdata at once, then refreshing
Closed for longer than keepthe entry is collected; the next opening fetchesloading
  • The native <dialog> gives the modal, the focus trap, Escape and the return of focus; the component only tracks open from its close event.
  • $props.id() gives each instance its own heading id for aria-labelledby.

Variations

Read outside the block

When the template reads the query outside {#if open} (a title, a count), when pauses it instead. Its data is kept while paused.

TypeScript
// Read outside {#if}: `when` pauses the query while the dialog is closed and keeps its data.
const member = createQuery(getMember, () => ({ id }), { when: () => open });

Prepare before the click

getMember.prefetch({ id }) on pointerenter fills the entry at low priority, so the dialog opens with data.