New to Lit Query? Start with Installation and Quick Start before wiring query controllers into your elements.
A query is a declarative dependency on an asynchronous source of data tied to a unique key. Use queries for reading server state. If a function creates, updates, or deletes server data, use a mutation instead.
In Lit, subscribe to a query with createQueryController:
import { LitElement, html } from 'lit'
import { createQueryController } from '@tanstack/lit-query'
class TodosView extends LitElement {
private readonly todos = createQueryController(this, {
queryKey: ['todos'],
queryFn: fetchTodos,
})
render() {
const query = this.todos()
if (query.isPending) return html`Loading...`
if (query.isError) return html`Error: ${query.error.message}`
return html`
<ul>
${query.data.map((todo) => html`<li>${todo.title}</li>`)}
</ul>
`
}
}import { LitElement, html } from 'lit'
import { createQueryController } from '@tanstack/lit-query'
class TodosView extends LitElement {
private readonly todos = createQueryController(this, {
queryKey: ['todos'],
queryFn: fetchTodos,
})
render() {
const query = this.todos()
if (query.isPending) return html`Loading...`
if (query.isError) return html`Error: ${query.error.message}`
return html`
<ul>
${query.data.map((todo) => html`<li>${todo.title}</li>`)}
</ul>
`
}
}The controller needs:
The returned accessor exposes the current QueryObserverResult. Call it in render, or read .current:
const query = this.todos()
const sameQuery = this.todos.currentconst query = this.todos()
const sameQuery = this.todos.currentA query can be in one primary state at a time:
The result also includes isFetching, which can be true during the initial load or a background refetch.
render() {
const query = this.todos()
if (query.status === 'pending') {
return html`<span>Loading...</span>`
}
if (query.status === 'error') {
return html`<span>Error: ${query.error.message}</span>`
}
return html`<todo-list .items=${query.data}></todo-list>`
}render() {
const query = this.todos()
if (query.status === 'pending') {
return html`<span>Loading...</span>`
}
if (query.status === 'error') {
return html`<span>Error: ${query.error.message}</span>`
}
return html`<todo-list .items=${query.data}></todo-list>`
}TypeScript will narrow query.data after you check pending and error before reading it.
The status field describes whether data is available. The fetchStatus field describes what the query function is doing:
These states are intentionally separate. Background refetching and stale-while-revalidate behavior can produce combinations like:
Use status when deciding whether data can be rendered, and use fetchStatus or isFetching when deciding whether to show a network activity indicator:
render() {
const query = this.todos()
if (query.isPending) return html`Loading...`
if (query.isError) return html`Error: ${query.error.message}`
return html`
${query.fetchStatus === 'fetching'
? html`<span>Refreshing...</span>`
: null}
<todo-list .items=${query.data}></todo-list>
`
}render() {
const query = this.todos()
if (query.isPending) return html`Loading...`
if (query.isError) return html`Error: ${query.error.message}`
return html`
${query.fetchStatus === 'fetching'
? html`<span>Refreshing...</span>`
: null}
<todo-list .items=${query.data}></todo-list>
`
}Use an options getter when the query key or query function depends on host state:
class UserTodos extends LitElement {
static properties = {
userId: { type: String },
}
userId = ''
private readonly todos = createQueryController(this, () => ({
queryKey: ['todos', this.userId],
queryFn: () => fetchTodos(this.userId),
enabled: this.userId.length > 0,
}))
}class UserTodos extends LitElement {
static properties = {
userId: { type: String },
}
userId = ''
private readonly todos = createQueryController(this, () => ({
queryKey: ['todos', this.userId],
queryFn: () => fetchTodos(this.userId),
enabled: this.userId.length > 0,
}))
}The query key is used for caching, refetching, and sharing data between controllers.
The accessor includes refetch:
html`<button @click=${() => this.todos.refetch()}>Refetch</button>`html`<button @click=${() => this.todos.refetch()}>Refetch</button>`For multiple queries that should run at the same time, see Parallel Queries.