Queries
A query is created with createQuery(options) (the registered default QueryClient is used) or createQuery(queryClient, options) (explicit client). It returns an object of effector stores and events.
Reactive query keys
Anywhere in queryKey, you can use a Store instead of a plain value. The query refetches automatically when any store updates.
const $userId = createStore(1)
const userQuery = createQuery({ name: 'user', queryKey: ['user', $userId], queryFn: ({ queryKey }) => fetchUser(queryKey[1] as number),})Mixing stores and primitives is supported:
queryKey: ['posts', 42, $section, { sort: 'asc' }]Enabled flag
enabled controls whether the query runs. It accepts a boolean OR a Store<boolean>:
// Staticconst userQuery = createQuery({ name: 'user', queryKey: ['user'], queryFn: fetchUser, enabled: false, // never fetches until enabled changes})
// Reactiveconst $isLoggedIn = createStore(false)
const profileQuery = createQuery({ name: 'profile', queryKey: ['profile'], queryFn: fetchProfile, enabled: $isLoggedIn, // fetches once $isLoggedIn becomes true})Dependent queries
Use one query’s $isSuccess as another’s enabled:
const userQuery = createQuery({ name: 'user', queryKey: ['user'], queryFn: fetchUser,})
const postsQuery = createQuery({ name: 'user-posts', queryKey: ['posts'], queryFn: fetchPosts, enabled: userQuery.$isSuccess,})select — narrow data shape
select runs after the queryFn and narrows the displayed TData type:
const userQuery = createQuery({ name: 'user', queryKey: ['user'], queryFn: () => fetchUser(), // returns { id, name, email, role } select: (data) => data.name, // $data: Store<string | undefined>})If select throws, the query transitions to error state with the thrown value.
placeholderData
Show data while the new key is loading:
import { keepPreviousData } from '@tanstack/query-core'
const todosQuery = createQuery({ name: 'todos', queryKey: ['todos', $page], queryFn: ({ queryKey }) => fetchTodos(queryKey[1]), placeholderData: keepPreviousData,})
todosQuery.$isPlaceholderData // Store<boolean>A static value or function is also supported:
placeholderData: { id: 0, name: 'Loading…' }
// Or a function that gets prevData and prevQueryplaceholderData: (prev) => prev,Polling with refetchInterval
const statusQuery = createQuery({ name: 'status', queryKey: ['status'], queryFn: fetchStatus, refetchInterval: 5000, // every 5 s})A function form lets you stop polling based on data:
refetchInterval: (q) => { const v = q.state.data as { done: boolean } | undefined return v?.done ? false : 1000},Reactive refetchInterval
refetchInterval also accepts a Store<number | false>. Toggling the store starts / stops polling at runtime — the library calls observer.setOptions({ refetchInterval }) on every store change, so the live observer picks up the new interval immediately.
import { createEvent, createStore } from 'effector'
const togglePolling = createEvent()const $interval = createStore<number | false>(3000).on( togglePolling, (v) => (v === false ? 3000 : false),)
const statusQuery = createQuery({ queryKey: ['status'], queryFn: fetchStatus, refetchInterval: $interval, // ← reactive})
// Anywhere in your app:togglePolling() // → stops / resumes pollingThis works under fork({ values }) too — each scope drives its own observer through the same store.
refetchOnMount / refetchOnWindowFocus / refetchOnReconnect
All three accept boolean | 'always' | (query) => boolean | 'always' and behave exactly as in TanStack Query. Defaults: true for mount/focus/reconnect.
createQuery({ name: 'auth', queryKey: ['auth'], queryFn: fetchAuth, refetchOnWindowFocus: 'always', // refetch even on fresh data refetchOnReconnect: false, // never refetch on reconnect})Manual refresh
userQuery.refresh() // invalidates the query and refetches in the backgroundReacting to fetch completion
finished.success / finished.failure are events you can drive sample from —
react to every completed fetch without watching $status by hand.
const userQuery = createQuery({ name: 'user', queryKey: ['user', $userId], queryFn: ({ queryKey }) => fetchUser(queryKey[1]),})
// Chain a dependent load off each successful fetch.sample({ clock: userQuery.finished.success, target: loadSettings,})
// Surface every failure.sample({ clock: userQuery.finished.failure, fn: (err) => `Failed: ${err.message}`, target: showToast,})finished.success carries the post-select data; finished.failure carries the
error. They fire on fresh fetches, refresh(), and reactive key changes — but
not for the baseline state seen on mount (e.g. SSR-hydrated cache). See the
createQuery lifecycle events reference
for the full semantics.
Lifecycle
You must call mounted() (or use useQuery(query) in React) for the observer to subscribe. unmounted() tears it down.
The observer is shared per Scope and reference-counted: every mounted() is one owner, and only the last matching unmounted() releases the observer. Several components and a feature-level sample can drive the same query independently — unmounting one of them doesn’t stop updates for the rest. Extra unmounted() calls are a safe no-op.
userQuery.mounted()// ...userQuery.unmounted() // last owner: cancels in-flight, releases observerIn React, the useQuery hook calls these for you.