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-tanstack

You 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-native

Wire 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);

React Native

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 queryThe error, tagged with the operation name and retry count, with the query key as context
Failed mutationThe same, taken from the mutation key
BreadcrumbsEvery query and mutation start, success and failure, in order, so a captured error arrives with the trail that led to it
SpansOne 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 captureYesYes
BreadcrumbsYesYes
Per-error tagsYesNo: the native SDK's captureException takes a level only
Structured contextYes, an objectSerialized to a string, since the native SDK takes strings
Spans / tracingYesNo: 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

OptionDefaultNotes
captureQueryErrorstrueReport failed queries
captureMutationErrorstrueReport failed mutations
breadcrumbstrueRecord the lifecycle trail
tracingtrueSpan per operation. Web only
redactKeynone(key, kind) => key, applied before anything is recorded
shouldCapturenone(error, context) => boolean
includeMutationVariablesfalseOff because payloads carry secrets
maxKeyLength256Serialized keys are truncated past this
onWarningnoneCalled 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: 0 without a flush().
  • A query key is missing from the issue. redactKey removed it, or it exceeded maxKeyLength and 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 holding Date, Map or undefined are 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.