Skip to content

Quick start

1. Set up the QueryClient

The QueryClient is the same object used by TanStack Query. Create one and call .mount() so it can subscribe to focus / online events. Then register it with setQueryClient — factories will use it automatically.

import { QueryClient } from '@tanstack/query-core'
import { setQueryClient } from '@effector-tanstack-query/core'
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
queryClient.mount()
setQueryClient(queryClient)

For SSR / per-request isolation, inject the client per scope via fork({ values: [[$queryClient, queryClient]] }) instead — see SSR.

2. Create a query

import { createQuery } from '@effector-tanstack-query/core'
export const userQuery = createQuery({
name: 'user',
queryKey: ['user', 1],
queryFn: () => fetch('/api/users/1').then((r) => r.json()),
})

Passing the client explicitly still works: createQuery(queryClient, options).

The name is optional but strongly recommended — it gives the internal stores stable SIDs so they round-trip via serialize(scope) / fork({ values }) for SSR. (Why?)

3. Read its state

createQuery returns an object with effector stores and events:

userQuery.$data // Store<User | undefined>
userQuery.$error // Store<Error | null>
userQuery.$status // Store<'pending' | 'success' | 'error'>
userQuery.$isPending // Store<boolean>
userQuery.$isFetching // Store<boolean>
userQuery.mounted // EventCallable<void> — start the subscription
userQuery.refresh // EventCallable<void> — invalidate + refetch

Drive the lifecycle yourself, then read state:

userQuery.mounted() // observer subscribes; query starts fetching
// ...await something
console.log(userQuery.$data.getState())

In tests with fork, inject a fresh client per scope:

import { fork, allSettled } from 'effector'
import { $queryClient } from '@effector-tanstack-query/core'
const queryClient = new QueryClient()
queryClient.mount()
const scope = fork({ values: [[$queryClient, queryClient]] })
await allSettled(userQuery.mounted, { scope })
expect(scope.getState(userQuery.$data)).toEqual({ id: 1, name: 'Alice' })

4. Use in React (optional)

import { useQuery } from '@effector-tanstack-query/react'
function UserProfile() {
const { data, isPending, error, refresh } = useQuery(userQuery)
if (isPending) return <p>Loading…</p>
if (error) return <p>Error: {error.message}</p>
return (
<div>
<h1>{data.name}</h1>
<button onClick={refresh}>Refresh</button>
</div>
)
}

The hook calls mounted() on mount and unmounted() on cleanup automatically.

5. Make the key reactive

Drop a Store into queryKey and the query refetches automatically when it updates.

import { createStore, createEvent } from 'effector'
const setUserId = createEvent<number>()
const $userId = createStore(1).on(setUserId, (_, id) => id)
const userQuery = createQuery({
name: 'user',
queryKey: ['user', $userId],
queryFn: ({ queryKey }) =>
fetch(`/api/users/${queryKey[1]}`).then((r) => r.json()),
})
userQuery.mounted()
setUserId(2) // → fires a refetch with key ['user', 2]

What’s next

  • Read Queries for enabled, placeholderData, select, and refetchInterval.
  • Read Mutations for mutateWith, finished events, createInvalidate, and offline behavior.
  • For full type signatures, see the API reference.
  • Browse runnable apps in examples/: examples/csr (Vite + React, every common pattern) and examples/ssr (Next.js App Router with query.prefetch).