Frequently asked questions
Comparison with other libs

Comparison with other libs

Prefetching

@hazae41/xswr

user.fetch()

swr

mutate('/api/user', fetch('/api/user').then(res => res.json()))

react-query

await queryClient.prefetchQuery(['user'], fetchUser)

Optimistic updates

@hazae41/xswr

Can use yield to represent fine-grained steps

document.update(async function* () {
  yield () => ({ data: "My optimistic document" })
  await new Promise(ok => setTimeout(ok, 1000))
  yield () => ({ data: "My optimistic document 2" })
  return await postAsJson("/api/edit", "My real document")
})

Can run multiple optimistic updates at the same time, in parallel

function onTitleChange(title: string) {
  document.update(async function* () {
    yield (previous) => ({ data: { ...previous!.data!, title } })
    return await postAsJson("/api/edit", { title })
  })
}
function onContentChange(content: string) {
  document.update(async function* () {
    yield (previous) => ({ data: { ...previous!.data!, content } })
    return await postAsJson("/api/edit", { content })
  })
}

swr

mutate('/api/todos', updateFn(user), { optimisticData: user, rollbackOnError: true })

react-query

useMutation(updateTodo, {
  onMutate: async newTodo => {
    await queryClient.cancelQueries(['todos'])
    const previousTodos = queryClient.getQueryData(['todos'])
    queryClient.setQueryData(['todos'], old => [...old, newTodo])
    return { previousTodos }
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos)
  },
  onSettled: () => {
    queryClient.invalidateQueries(['todos'])
  },
})

Cancellation

@hazae41/xswr

user.aborter.abort()

swr

Unsupported.

react-query

queryClient.cancelQueries(['todos'])

Garbage collection

@hazae41/xswr

Global, per-query, and per-fetch expiration time. You can use response headers like Cache-Control to set an expiration time.

async function fetcher(url: string) {
  const res = await fetch(url)
  const expiration = Expiration.from(res.headers)
 
  if (!res.ok) {
    const error = new Error(await res.text())
    return { error, expiration }
  }
 
  const data = await res.json()
  return { data, expiration }
}

swr

Unsupported.

react-query

Global and per-query expiration time. You can only define an expiration time at global scope or query scope.

Persistent storage

@hazae41/xswr

Per-query persistent storage. You can set a query as persistent in its schema. Out of the box support for IndexedDB and LocalStorage.

const storage = useAsyncLocalStorage()
const query = useQuery(`/api/hello`, fetcher, { storage })

swr

Partial support. You have to create your own storage or install third party ones.

react-query

Global persistent storage. You persist your whole app and define excluded queries using shouldDehydrateQuery. You have to install extra dependencies.

Store normalization

@hazae41/xswr

Very simple. Out of the box. No dependency needed.

async function normalizer(data: Data, more: NormalizerMore) {
  return await Promise.all(data.items.map(item => getItemRef(item)))
}
 
const query = useQuery(`/api/`, fetcher, { normalizer })

swr

Unsupported.

react-query

Unsupported.

React Suspense

@hazae41/xswr

Super natural and easy. Doesn't enforce any pattern and doesn't require any configuration. Partially compatible with SSR.

function Component() {
  const { data, error, suspend } = useData()
 
  // Throw the error
  if (error) throw error
 
  // Fetch and suspend until next state change
  if (!data) throw suspend()
 
  return <div>{JSON.stringify(data)}</div>
}

swr

Forces you to use an ErrorBoundary. No control over when to throw and when to suspend. Not compatible with SSR.

react-query

Seems simple at first but you have to use a configuration for it to work like you want. Error cleaning requires even more code. Compatibility with SSR? Unknown.