# Observers

Reactive subscriptions with status deduplication for queries, infinite queries, and mutations

Observers bridge gpui-query's resources to GPUI's reactivity. An observer attaches to a resource entity and calls `cx.notify()` to trigger a re-render, but only when the resource's status changes. This is the primitive that keeps `use_query`, `use_infinite_query`, and `use_mutation` from re-rendering your view on every internal state mutation.

## The Observer type

There is a single generic implementation, `Observer<R>`, parameterized over the resource kind. Three type aliases name the concrete observers you will use:

```rust
// All three are Observer<R> with a different R / Status pair.
use gpui_query::client::{
    QueryObserver, InfiniteQueryObserver, MutationObserver,
};

type QueryObserver<T, E>          = Observer<QueryResource<T, E>>;          // Status: QueryStatus
type InfiniteQueryObserver<T, E>  = Observer<InfiniteQueryResource<T, E>>;  // Status: QueryStatus
type MutationObserver<V, T, E>    = Observer<MutationResource<V, T, E>>;    // Status: MutationStatus
```

Each resource exposes its status via an inherent `status()` method; the `ObservableResource` trait (pub-crate) surfaces it generically so `Observer<R>` can deduplicate notifications for any resource kind.

## Status deduplication

This is the whole point of the observer. Without it, a raw `cx.observe` would call `cx.notify()` on every entity mutation, including intermediate ones like `increment_retry()` that leave the status at `Loading`. A query retrying three times would re-render the view two or three extra times for nothing.

`Observer` tracks the last status it saw and notifies only on change:

```rust
// (sketch of the dedup logic inside Observer::observe)
let last_status: Cell<Option<R::Status>> = Cell::new(None);

cx.observe(&upgraded, move |_, entity, cx| {
    let current = entity.read(cx).observable_status();
    if notify_on_change {
        if last_status.get() != Some(current) {
            last_status.set(Some(current));
            cx.notify(); // status actually changed
        }
    } else {
        cx.notify(); // unconditional mode
    }
});
```

The result: `increment_retry()` / `prepare_retry()` calls (status stays `Loading`) no longer trigger re-renders. Your view re-renders on the transitions that matter: `Idle → LoadingEmpty → Success`, `Success → LoadingWithData → Success`, `Loading → Failure`, and so on.

## Creating and observing

Build an observer from an entity, then call `observe(cx)` to start watching. `observe` returns `Option<Subscription>` (`None` if the entity was already dropped). Store the returned subscription; dropping it ends the observation.

```rust
use gpui_query::client::QueryObserver;
# use gpui_query::QueryResource;
# #[derive(Clone)] struct T;
# #[derive(Clone, Debug)] struct E;

struct MyView {
    users: gpui::Entity<QueryResource<T, E>>,
    _subscription: gpui::Subscription,
}

impl MyView {
    fn new(users: gpui::Entity<QueryResource<T, E>>, cx: &mut gpui::Context<Self>) -> Self {
        let observer = QueryObserver::<T, E>::new(&users);
        let subscription = observer.observe(cx)
            .expect("entity was just created; cannot be dropped");
        Self { users, _subscription: subscription }
    }
}
```

> The hooks (`use_query`, `use_infinite_query`, `use_mutation`) already construct the observer and return the subscription for you. You only need to create an observer yourself when you are subscribing to a resource you obtained another way (for example, one you pulled out of the `QueryClient` with `query::<T, E>(key)`).

## ObserverConfig

`ObserverConfig::notify_on_status_change_only` controls the dedup behavior. It defaults to `true`, which is what you almost always want. Set it to `false` to get unconditional notifications on every entity mutation:

```rust
use gpui_query::client::{Observer, ObserverConfig};
# use gpui_query::QueryResource;
# #[derive(Clone)] struct T;
# #[derive(Clone, Debug)] struct E;
# fn doc(entity: gpui::Entity<QueryResource<T, E>>) {
# let cx: &mut gpui::Context<()> = unimplemented!();

let sub = Observer::new(&entity)
    .with_config(ObserverConfig { notify_on_status_change_only: false })
    .observe(cx);
# }
```

Use unconditional mode only when you genuinely need to react to non-status fields (a custom retry counter you display inline, for example). It is strictly more re-renders.

## `Option<Subscription>`

`observe()` returns `Option<Subscription>` rather than panicking. In debug builds the hooks `panic!` if this is `None` right after creating an entity (it would indicate a GPUI internal regression); in release builds they fall back to a no-op subscription so a misbehaving observer cannot crash the app. When you construct an observer yourself, decide explicitly: `.expect(...)` if a `None` is impossible in your context, or `match` / `let else` to handle it gracefully.

## How the hooks use it

| Hook                  | Observer                   | Status type     |
| --------------------- | -------------------------- | --------------- |
| `use_query`           | `QueryObserver<T, E>`      | `QueryStatus`   |
| `use_infinite_query`  | `InfiniteQueryObserver<T, E>` | `QueryStatus` |
| `use_mutation`        | `MutationObserver<V, T, E>`| `MutationStatus`|

Each hook creates its observer, calls `observe(cx)`, and returns the subscription as the second element of its tuple. The status-dedup is what makes a retrying mutation or a refetching query cheap to render: only terminal status changes reach your view.

## ObservableResource

`ObservableResource` is the pub(crate) trait that lets `Observer<R>` read a status generically. It is not part of the public API, but it is why the three resource kinds share one observer implementation: each resource exposes `status()` with its own associated `Status` type, and `Observer` deduplicates on that. If you write your own resource type that should be observable, it will need the same `status()` method shape, but for application code, the three built-in observers cover every resource the hooks produce.

## Next steps

- [Queries](/docs/api/queries) / [Infinite queries](/docs/api/infinite-queries) / [Mutations](/docs/api/mutations): the hooks that build observers for you.
- [DevTools](/docs/advanced/devtools): diagnostics read the same live entities observers watch.
- [Error handling](/docs/guides/error-handling): why `Cancelled` rarely reaches your UI (it is a status, deduplicated like any other).
