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.