Skip to content

Instantly share code, notes, and snippets.

@jakubriedl
Last active June 10, 2023 04:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakubriedl/812c2a7b26927a2249a4719555d9a0ca to your computer and use it in GitHub Desktop.
Save jakubriedl/812c2a7b26927a2249a4719555d9a0ca to your computer and use it in GitHub Desktop.
URQL batchFetchExchange
import DataLoader from 'dataloader'
import { Exchange, Operation } from 'urql'
import { pipe, map } from 'wonka'
interface BatchRequest {
url: string
options?: RequestInit
}
const batchFetch = (
loader: DataLoader<BatchRequest, Response>,
fetcher: typeof fetch,
): typeof fetch => (url: RequestInfo, options?: RequestInit) => {
if (
options?.method !== 'GET' ||
typeof url !== 'string' ||
!url.includes('extensions=%7B%22persistedQuery%22')
) {
return fetcher(url, options)
}
return loader.load({ url, options })
}
const loadBatch = (fetcher: typeof fetch) => async (
reqs: Readonly<BatchRequest[]>,
) => {
// if batch has just one item don't batch it as it will use persisted queries
if (reqs.length === 1) return [await fetcher(reqs[0].url, reqs[0].options)]
const requestBody = reqs
.map((req) => new URL(req.url).searchParams)
.map((params) => ({
operationName: params.get('operationName'),
variables: JSON.parse(params.get('variables') ?? '{}'),
extensions: JSON.parse(params.get('extensions') ?? '{}'),
}))
const urlObj = new URL(reqs[0].url)
urlObj.searchParams.delete('operationName')
urlObj.searchParams.delete('variables')
urlObj.searchParams.delete('extensions')
const response = await fetcher(urlObj.toString(), {
...reqs[0].options,
method: 'POST',
body: JSON.stringify(requestBody),
})
const bodies: object[] = await response.json()
return bodies.map((body: object) => {
return {
...response,
json: () => body,
} as Response
})
}
const shouldBatch = (operation: Operation): boolean => {
return operation.operationName === 'query' && !!operation.context.batch
}
export const batchFetchExchange = (
options?: DataLoader.Options<BatchRequest, Response>,
fetcher = fetch,
): Exchange => ({ forward }) => {
const loader = new DataLoader(loadBatch(fetcher), options)
return (ops$) =>
pipe(
ops$,
map((operation: Operation) => ({
...operation,
context: {
...operation.context,
fetch: shouldBatch(operation)
? batchFetch(loader, operation.context.fetch ?? fetcher)
: operation.context.fetch,
},
})),
forward,
)
}
// with Next.js in _app.tsx
const mergeExchanges: MergeExchanges = (ssrExchange) => [
dedupExchange,
cacheExchange,
ssrExchange,
batchFetchExchange(),
persistedFetchExchange,
fetchExchange,
]
export default withUrqlClient({ url: "https://example.com" }, mergeExchanges)(RootApp)
// enable batch for query
const [{ data }] = useQuery({
query: someQuery,
variables: {...}
context: React.useMemo(() => ({ batch: true }), []), // needs to be memoized otherwise urql will infinitely reload the query
})
@reconbot
Copy link

No longer works with urql 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment