TanStack Query
BugWatch for TanStack Querylink
@newinstance/bugwatch-tanstack connects BugWatch to TanStack Query. Point it at a QueryClient and every failed query and mutation is reported, with the operation that failed, the breadcrumb trail that led there, and a span for the request.
One package covers React Web and React Native.
Installlink
npm install @newinstance/bugwatch-tanstackYou also need TanStack Query and the BugWatch SDK for your platform:
# React Web
npm install @tanstack/react-query @newinstance/bugwatch
# React Native
npm install @tanstack/react-query @newinstance/bugwatch-react-nativeWire it uplink
Initialise BugWatch as usual, then instrument the client once, where you create it. Nothing else in the app changes, and no query or mutation needs to know BugWatch exists.
React Web
import { QueryClient } from '@tanstack/react-query';
import { BugWatch } from '@newinstance/bugwatch';
import { instrumentQueryClientWithBugWatch } from '@newinstance/bugwatch-tanstack/web';
BugWatch.init({ dsn: process.env.BUGWATCH_DSN! });
export const queryClient = new QueryClient();
instrumentQueryClientWithBugWatch(queryClient);import { QueryClient } from '@tanstack/react-query';
import { BugWatch } from '@newinstance/bugwatch-react-native';
import { instrumentQueryClientWithBugWatch } from '@newinstance/bugwatch-tanstack/native';
BugWatch.init({ dsn: BUGWATCH_DSN });
export const queryClient = new QueryClient();
instrumentQueryClientWithBugWatch(queryClient);Instrumenting returns a function that removes it again:
const stop = instrumentQueryClientWithBugWatch(queryClient);
stop();What is reportedlink
| Sent to BugWatch | |
|---|---|
| Failed query | The error, tagged with the operation name and retry count, with the query key as context |
| Failed mutation | The same, taken from the mutation key |
| Breadcrumbs | Every query and mutation start, success and failure, in order, so a captured error arrives with the trail that led to it |
| Spans | One span per query and mutation, carrying duration and failure status (web only, see below) |
The operation name is the first element of the query or mutation key, so ['users', 'list'] reports as users.
Platform differenceslink
The two BugWatch SDKs are not identical, and the adapter does not pretend otherwise.
Web (@newinstance/bugwatch) | React Native (@newinstance/bugwatch-react-native) | |
|---|---|---|
| Error capture | Yes | Yes |
| Breadcrumbs | Yes | Yes |
| Per-error tags | Yes | No: the native SDK's captureException takes a level only |
| Structured context | Yes, an object | Serialized to a string, since the native SDK takes strings |
| Spans / tracing | Yes | No: the native SDK has no span API |
Tracing is on by default. On React Native it is unavailable, so pass onWarning if you want to hear about that rather than wonder why no spans appear:
instrumentQueryClientWithBugWatch(queryClient, {
onWarning: (message) => console.warn(message),
});Errors and breadcrumbs still work on React Native; only spans are missing. The returned function carries tracingActive if you need to branch on it.
Keeping personal data out of BugWatchlink
Query keys routinely hold user ids, emails and search terms, and they are recorded with every captured error. redactKey runs before anything leaves the app:
instrumentQueryClientWithBugWatch(queryClient, {
redactKey: (key) =>
key.map((part) =>
typeof part === 'string' && part.includes('@') ? '[redacted]' : part,
),
});Mutation variables are never recorded by default, because that is where card numbers and passwords live. Opt in only where the payload is known to be safe, and redactKey is applied to them too:
instrumentQueryClientWithBugWatch(queryClient, { includeMutationVariables: true });Not reporting expected failureslink
A 404 from a lookup is usually not a bug. shouldCapture decides:
instrumentQueryClientWithBugWatch(queryClient, {
shouldCapture: (error, context) => {
if (error instanceof HttpError && error.status === 404) return false;
if (context.kind === 'query' && context.operationName === 'presence') return false;
return true;
},
});Filtered errors still leave a breadcrumb, so the trail stays complete even when the error itself is not worth an issue.
Optionslink
| Option | Default | Notes |
|---|---|---|
captureQueryErrors | true | Report failed queries |
captureMutationErrors | true | Report failed mutations |
breadcrumbs | true | Record the lifecycle trail |
tracing | true | Span per operation. Web only |
redactKey | none | (key, kind) => key, applied before anything is recorded |
shouldCapture | none | (error, context) => boolean |
includeMutationVariables | false | Off because payloads carry secrets |
maxKeyLength | 256 | Serialized keys are truncated past this |
onWarning | none | Called when a requested feature is unavailable on this platform |
Custom targetslink
instrumentQueryClient accepts any object implementing BugWatchTarget, which is useful in tests or when routing to your own sink:
import { instrumentQueryClient } from '@newinstance/bugwatch-tanstack';
instrumentQueryClient(queryClient, {
platform: 'web',
capabilities: { captureTags: true, tracing: false, richContext: true },
captureException: (error) => myReporter.report(error),
addBreadcrumb: (crumb) => myReporter.trail(crumb),
setContext: (key, data) => myReporter.context(key, data),
});Troubleshootinglink
- Nothing is reported.
BugWatch.init()must run before the first query. Instrumenting a client is not enough on its own. - Errors arrive but no spans. Expected on React Native, which has no span API. On web, check that the DSN is a project key and that spans are not being dropped by
flushInterval: 0without aflush(). - A query key is missing from the issue.
redactKeyremoved it, or it exceededmaxKeyLengthand was truncated. - Circular query keys throw. TanStack Query hashes keys with
JSON.stringify, so this fails inside TanStack itself before the adapter is involved. Keys holdingDate,Maporundefinedare handled.
Every call into BugWatch is wrapped: if the SDK is missing, misconfigured or throws, queries and mutations resolve and reject exactly as they would without the adapter.