Skip to content
Hooks

useQuery

Bridges TanStack Query with Legend-State. Returns query state as an Observable, and accepts Observable values anywhere in the options — including individual elements inside queryKey. When an observable value changes, the query automatically re-fetches. Options follow TanStack Query’s standard UseQueryOptions — refer to TanStack Query docs for full option details. Each option field accepts MaybeObservable<T> for reactive control.

Both variants require a QueryClientProvider ancestor — the client is injected from context.

useQuery
Loading

Select a category — the query auto-refetches via Observable queryKey.

Fetching...
import {
function For<T, TProps>({ each, optimized: isOptimized, item, itemProps, sortValues, children, }: {
each?: ObservableParam<T[] | Record<any, T> | Map<any, T>>;
optimized?: boolean;
item?: FC<ForItemProps<T, TProps>>;
itemProps?: TProps;
sortValues?: (A: T, B: T, AKey: string, BKey: string) => number;
children?: (value: Observable<T>, id: string | undefined) => ReactElement;
}): ReactElement | null (+1 overload)
For
,
function Show<T>(props: Props<T>): ReactElement (+2 overloads)
Show
,
function useObservable<T>(): Observable<T | undefined> (+3 overloads)

A React hook that creates a new observable

@paraminitialValue The initial value of the observable or a function that returns the initial value

@seehttps://www.legendapp.com/dev/state/react/#useObservable

useObservable
} from "@usels/core";
import {
const useQuery: <TData = unknown>(options?: DeepMaybeObservable<CreateQueryOptions<TData>>) => Observable<QueryState<TData>>
useQuery
} from "@usels/tanstack-query";
function
function ProductList(): JSX.Element
ProductList
() {
const
const query: any
query
=
useQuery<unknown>(options?: DeepMaybeObservable<CreateQueryOptions<unknown>>): Observable<QueryState<unknown>>

Core observable function for bridging TanStack Query with Legend-State.

Must be called inside a scope (e.g. useScope factory or standalone createScope) wrapped by a QueryClientProvidergetQueryClient() injects the client from context. Reactive teardown is registered via onUnmount; option changes (including Observable elements inside queryKey) are tracked via observe.

@paramoptions - Plain object, per-field Observable, or outer Observable of options.

@returnsObservable reflecting query state (including refetch).

useQuery
({
queryKey: {}
queryKey
: ["products"],
queryFn: () => any
queryFn
: () =>
any
fetch
("/api/products").
any
then
((
r: any
r
) =>
r: any
r
.
any
json
()),
});
const
const products$: any
products$
=
useObservable<unknown>(value: Promise<unknown> | (() => unknown) | unknown, deps?: DependencyList): any (+3 overloads)

A React hook that creates a new observable

@paraminitialValue The initial value of the observable or a function that returns the initial value

@seehttps://www.legendapp.com/dev/state/react/#useObservable

useObservable
(() =>
const query: any
query
.
any
data
.
any
get
() ?? []);
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
<
function Show<T>(props: Props<T>): ReactElement (+2 overloads)
Show
if: any
if
={
const query: any
query
.
any
isLoading
}>
<
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>Loading...</
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
</
function Show<T>(props: Props<T>): ReactElement (+2 overloads)
Show
>
<
function Show<T>(props: Props<T>): ReactElement (+2 overloads)
Show
if: any
if
={
const query: any
query
.
any
isError
}>
<
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>Error: {
const query: any
query
.
any
error
.
any
get
()?.
any
message
}</
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
</
function Show<T>(props: Props<T>): ReactElement (+2 overloads)
Show
>
<
function For<T, TProps>({ each, optimized: isOptimized, item, itemProps, sortValues, children, }: {
each?: ObservableParam<T[] | Record<any, T> | Map<any, T>>;
optimized?: boolean;
item?: FC<ForItemProps<T, TProps>>;
itemProps?: TProps;
sortValues?: (A: T, B: T, AKey: string, BKey: string) => number;
children?: (value: Observable<T>, id: string | undefined) => ReactElement;
}): ReactElement | null (+1 overload)
For
each?: any
each
={
const products$: any
products$
}>{(
p$: any
p$
) => <
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>{
p$: any
p$
.
any
name
.
any
get
()}</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>}</
function For<T, TProps>({ each, optimized: isOptimized, item, itemProps, sortValues, children, }: {
each?: ObservableParam<T[] | Record<any, T> | Map<any, T>>;
optimized?: boolean;
item?: FC<ForItemProps<T, TProps>>;
itemProps?: TProps;
sortValues?: (A: T, B: T, AKey: string, BKey: string) => number;
children?: (value: Observable<T>, id: string | undefined) => ReactElement;
}): ReactElement | null (+1 overload)
For
>
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
);
}

When an element inside queryKey is an Observable, the query automatically re-fetches whenever its value changes. Use .peek() in queryFn to read the current value without registering an extra reactive dependency.

import { useQuery } from "@usels/tanstack-query";
import { observable } from "@usels/core";
const id$ = observable("1");
function UserProfile() {
const user$ = useQuery({
queryKey: ["users", id$],
queryFn: () => fetchUser(id$.peek()),
});
return <p>{user$.data.get()?.name}</p>;
}

The resolved queryKey is a plain array (e.g. ['users', '1']), so cache lookups via queryClient.getQueryData(['users', '1']) work as expected.

Observable inside a nested object in queryKey

Section titled “Observable inside a nested object in queryKey”

Observable values nested inside plain objects within queryKey are also resolved reactively.

import { useQuery } from "@usels/tanstack-query";
import { observable } from "@usels/core";
const filter$ = observable({ category: "electronics" });
function ProductList() {
const list$ = useQuery({
queryKey: ["products", { filter: filter$.category }],
queryFn: () => fetchProducts(filter$.category.peek()),
});
return <div>{list$.data.get()?.length} items</div>;
}

Individual options like enabled, staleTime, etc. also accept Observable values.

import { useQuery } from "@usels/tanstack-query";
import { observable } from "@usels/core";
const enabled$ = observable(false);
function Dashboard() {
const data$ = useQuery({
queryKey: ["dashboard"],
queryFn: fetchDashboard,
enabled: enabled$,
});
return <div>{data$.status.get()}</div>;
}
import { useQuery } from "@usels/tanstack-query";
function DataPanel() {
const query = useQuery({ queryKey: ["stats"], queryFn: fetchStats });
return (
<div>
<p>Updated: {new Date(query.dataUpdatedAt.get()).toLocaleTimeString()}</p>
<button onClick={() => query.refetch()}>Refresh</button>
</div>
);
}
export type { CreateQueryOptions, QueryState } from "./core";
export { createQuery } from "./core";
/**
* UseQueryOptions is an alias for CreateQueryOptions for backward compatibility.
*/
export type UseQueryOptions<TData = unknown> = CreateQueryOptions<TData>;
/**
* Custom hook that bridges TanStack Query with Legend-State.
* Manages query state as an observable using QueryObserver.
*
* Accepts `DeepMaybeObservable<CreateQueryOptions>`, supporting an Observable
* for the entire options object or for individual fields. Elements inside
* the queryKey array can also be Observables and will react to changes automatically.
*
* @example
* ```tsx
* const products$ = useQuery({
* queryKey: ['products'],
* queryFn: () => fetch('/api/products').then(r => r.json())
* })
* ```
*/
export type UseQuery = typeof createQuery;
export declare const useQuery: UseQuery;

View on GitHub