Skip to main content

React Query Comparison

While Zedux doesn't (yet!) provide helpers for query operations (infinite scrolling, pagination, etc), Zedux is currently patterned after React Query more than any other library (yes, more than Redux or Recoil). React Query's cache management is unparalleled. So Zedux's atomic model borrows a lot of ideas from it.

However, the purpose of Zedux is very different from React Query. React Query is designed around promises - managing their state, side effects, and result caches. Zedux is designed to manage both application state (a la Redux/Recoil) and cached server data. Zedux has some special features for handling promises, but currently very little compared to React Query.

Queries

Structurally, Zedux atoms are very similar to React Query queries.

  • Query keys and atom keys are constructed from a "deterministic" hash of their parameters.
  • For destroying stale instances: Atoms have ttl. Queries have cacheTime.
  • For suspense: Atoms have .setPromise(). Queries have suspense.
  • For tracking promise state: Zedux has query atoms and injectPromise(). Queries track promise state by default.

A side-by-side comparison, using suspense:

// React Query:
function usePosts() {
return useQuery(
'posts',
async () => {
const { data } = await axios.get('/posts')
return data
},
{ suspense: true }
)
}
// ... in a component:
const { data } = usePosts()

// Zedux:
const postsAtom = atom('posts', () => {
const fetchPosts = async () => {
const { data } = await axios.get('/posts')
return data
}

// `return api(promise)` is how you make query atoms in Zedux
return api(fetchPosts())
})
// ... in a component:
const { data } = useAtomValue(postsAtom)

Note that atom state factories can't be asynchronous functions. This is because the function body is used to dynamically inject dependencies, building Zedux's DI graph.

Dependent Queries

In React Query, it's up to components to specify query dependencies by passing the enabled option to useQuery.

const { data: user } = useQuery(['user', email], getUserByEmail)

const userId = user?.id

const { isIdle, data: projects } = useQuery(
['projects', userId],
getProjectsByUser,
{ enabled: !!userId }
)

In Zedux, atoms are more autonomous. They specify their own dependencies.

const userProjectsAtom = atom('userProjects', (email: string) => {
const user = injectAtomValue(userByEmail, [email])

const userId = user?.id

return injectPromise(() => getProjectsByUser(userId), [userId])
})

Zedux doesn't have an idle state (currently). The above example would be success until userByEmail updates with a value. If you need an idle state, feel free to open a GitHub discussion!

Mutations

Zedux doesn't have a concept of mutations. Mutation functions are typically exported from atoms, but many approaches are possible. Here's an example grouping query and mutation operations together in a single atom:

const usersAtom = atom('users', () => {
// a "query"
const fetchUsers = async () => {
const { data } = await axios.get('/users')
return data
}

const promiseApi = injectPromise(fetchUsers, [])

// a "mutation"
const updateUser = (id: string, user: User) => {
// we could also easily add an optimistic update right here
await axios.post(`/users/${id}`, user)

// we can do a full refetch (this mimics React Query most closely):
promiseApi.store.setState(await fetchUsers()) // essentially an invalidation
// or just update the record we know changed:
promiseApi.store.setStateDeep({ [id]: user })
// moving that above the .post() is all it takes to do an optimistic update
}

// The query controls the state of this atom. The mutation is exported:
return promiseApi.addExports({ updateUser })
})
// ... in a component:
const [{ data }, { updateUser }] = useAtomState(usersAtom)

Invalidation

Because of the mutation differences, Zedux and React Query also have very different concepts of invalidation. React Query is designed around invalidating caches manually with invalidateQueries(), especially after mutations.

Zedux allows manual invalidation via instance.invalidate(), but use cases for it are rare. Instead, the two approaches demonstrated in the above usersAtom example are common ways to go about it:

  • Precisely update state and let updates cascade through the atom graph, automatically rerunning atom state factories/injectEffect effects.
  • Manually rerun "query" functions like in the above fetchUsers example and directly "invalidate" state via store.setState().

QueryClient

Zedux ecosystems are patterned after React Query's QueryClient. They both create autonomous environments for managing [atoms|queries] that can be used outside React and are especially useful for testing.

Stream Support

React Query can work with sockets without too much hassle. But React Query is designed for promises. Zedux atoms are designed to support any asynchronous architecture.

This means that (for now) Zedux doesn't provide any promise-specific helpers like React Query's onSuccess, refetchOnWindowFocus, retryDelay, etc. The upside is, working with sockets/data streaming paradigms is more natural with Zedux.

import { atom, injectAtomValue, injectEffect, injectStore } from '@zedux/react'
import { io } from 'socket.io-client'

const socketAtom = atom('socket', () => io())

const messagesAtom = atom('messages', () => {
const socket = injectAtomValue(socketAtom)
const store = injectStore([])

injectEffect(() => {
const handler = message => {
store.setState(messages => [...messages, message])
}

socket.on('message', handler)

return () => socket.off('message', handler)
}, [socket])

return store
})

This is more flexible, but can require more boilerplate for promises. Most functionality can be abstracted, of course. We may provide a @zedux/query package someday that provides a full suite of tools for handling promises.

Using Both

While Zedux and React Query have many things in common, it is possible to use both libraries together.

The recommended approach is to use a React Query "bridge" atom to synchronize all of React Query's internal state with the atoms universe. This lets you use all the advanced query features of React Query combined with the powerful atomic state derivations of Zedux.

See the React Query Integration example.