Skip to content
gpui-query
Back to blog

Why gpui-query

Bringing TanStack Query's battle-tested async state patterns to GPUI and Rust — without the boilerplate.

hmziqrs
gpuirustasyncintroduction

When you build an editor like Zed, your components constantly need data that arrives asynchronously: language-server responses, file listings, collaborator presence, telemetry. The GPUI render loop is synchronous, which means juggling in-flight requests, caches, retries, and stale data by hand gets old fast.

gpui-query adapts TanStack Query's proven model to Rust and GPUI. It keeps the ideas you already know — declarative queries, automatic caching, background refetching, cooperative cancellation — and grounds them in Rust's type system and GPUI's reactive primitives.

Three layers, one crate

The crate is split into layers so you can adopt only what you need:

  • core — a Serde-only state machine (QueryResource, CachePolicy, RetryPolicy, QuerySignal). Framework-agnostic and serializable.
  • client — the GPUI QueryClient registry, with type-partitioned buckets for resource lifecycle, garbage collection, and persistence.
  • hook — the ergonomic use_query / use_mutation hooks that components subscribe through.

A query in five lines

use gpui_query::hook::use_query;
use gpui_query::{CachePolicy, QueryKey, RequestPolicy};

let (users, _subscription) = use_query(
    QueryKey::from(["users"]),
    CachePolicy::Ttl { ttl_ms: 60_000 },
    RequestPolicy::LatestWins,
    || async {
        let resp = reqwest::get("/api/users").await?;
        let users: Vec<User> = resp.json().await?;
        Ok(users)
    },
    cx,
);

use_query returns a tuple (data, status) by Rust convention — ergonomic to destructure, with the status enum carrying the full lifecycle state. You never write useEffect glue or manage a loading boolean by hand.

What you get for free

  • Caching via CachePolicy (NoCache, Ttl, StaleWhileRevalidate).
  • Retries with fixed delays or exponential backoff and a cap.
  • Cooperative cancellation through QuerySignal (Arc<AtomicBool>) so a component unmounting does not leak work.
  • Persistence via the QueryPersister trait — serialize and restore cache state across sessions.
  • Infinite queries for paginated data, with bidirectional fetching.

Who is this for?

If you are building anything nontrivial on GPUI and find yourself re-implementing a cache, a refetch timer, or a cancellation token, gpui-query exists for you. The core layer is testable in isolation; the hook layer slots straight into your views. Give it a try and open an issue if anything feels off.